local engine_controller = peripheral.find("EngineController")
local flight_control, hologram_manager, hologram_prop, controllers, system, properties, monitorUtil, shipNet_p2p_send, scanner, radar, replay_listener
local shutdown_flag, engineOff = false, false
local public_protocol = "shipNet_broadcast"
local protocol, missile_protocol, request_protocol = "CBCNetWork", "CBCMissileNetWork","CBCcenter"
local shipName, computerId = engine_controller.getName(), os.getComputerID()
local shipNet_list = {}
local beat_ct, call_ct, captcha, calling
local childShips, callList, linkedCannons = {}, {}, {}
local dimension = "overworld"
peripheral.find("modem", rednet.open)

local modelist = {
    { name = "宇宙飞船",  flag = false },
    { name = "四轴无人机",    flag = false },
    { name = "直升机", flag = false },
    { name = "飞艇",    flag = false },
    { name = "HMS飞行",    flag = false },
    { name = "跟随",     flag = false },
    { name = "回家",     flag = false },
    { name = "路径跟随", flag = false },
    { name = "飞船摄像头", flag = false },
    { name = "飞船跟随", flag = false },
    { name = "锚泊",  flag = false },
    { name = "太空第一人称",   flag = false },
    { name = "固定翼", flag = false },
}

local entryList = {
    "顶部",
    "底部",
    "左侧",
    "右侧",
    "前方",
    "后方"
}

local language = {
    "中文",
    "英文"
}

shell.openTab("shell")
----------------------------------------------------

local formatN = function(val, n)
    n = math.pow(10, n or 1)
    val = tonumber(val)
    return math.floor(val * n) / n
end

local genStr = function(s, count)
    local result = ""
    for i = 1, count, 1 do
        result = result .. s
    end
    return result
end

local getColorDec = function(paint)
    paint = string.byte(string.sub(paint, 1, 1))
    local result
    if paint == 48 then
        result = 1
    elseif paint > 96 and paint < 103 then
        result = 2 ^ (paint - 87)
    elseif paint > 48 and paint < 58 then
        result = 2 ^ (paint - 48)
    end
    return result
end

local getNextColor = function(color, index)
    local num = string.byte(string.sub(color, 1, 1))
    num = num + index
    if num < 48 then num = 102 end
    if num == 58 then num = 97 end
    if num == 103 then num = 48 end
    if num == 96 then num = 57 end
    return string.char(num)
end

local tableHasValue = function(targetTable, targetValue)
    for index, value in ipairs(targetTable) do
        if index ~= 'metatable' and value == targetValue then
            return true
        end
    end
    return false
end

local joinArrayTables = function(...)
    local entries = {}
    for i = 1, select('#', ...) do
        local t = select(i, ...)
        for _, v in ipairs(t) do
            table.insert(entries, v)
        end
    end
    return entries
end

local arrayTableDuplicate = function(targetTable)
    local entries = {}
    local seenValues = {}
    for i, v in ipairs(targetTable) do
        if not seenValues[v] then
            seenValues[v] = true
            table.insert(entries, v)
        end
    end
    return entries
end

local arrayTableRemoveElement = function(targetTable, value)
    for i, v in ipairs(targetTable) do
        if v == value then
            table.remove(targetTable, i)
            return
        end
    end
end

local copysign = function(num1, num2)
    num1 = math.abs(num1)
    num1 = num2 > 0 and num1 or -num1
    return num1
end

local genCaptcha = function(len)
    local length = len and len or 5
    local result = ""
    for i = 1, length, 1 do
        local num = math.random(0, 2)
        if num == 0 then
            result = result .. string.char(math.random(65, 90))
        elseif num == 1 then
            result = result .. string.char(math.random(97, 122))
        else
            result = result .. string.char(math.random(48, 57))
        end
    end
    return result
end

function table.contains(table, element)
    for _, value in pairs(table) do
        if value == element then
            return true
        end
    end
    return false
end

local split = function(input, delimiter)
    input = tostring(input)
    delimiter = tostring(delimiter)
    if (delimiter == "") then return false end
    local pos, arr = 0, {}
    for st, sp in function() return string.find(input, delimiter, pos, true) end do
        table.insert(arr, string.sub(input, pos, st - 1))
        pos = sp + 1
    end
    table.insert(arr, string.sub(input, pos))
    return arr
end

local stringToCharArray = function(str)
    local charArray = {}
    for i = 1, #str do
        charArray[i] = string.sub(str, i, i)
    end
    return charArray
end

table.copy = function (t)
    local tmp = {}
    for k, v in pairs(t) do
        if type(v) == "table" then
            local tmpv = table.copy(v)
            tmp[k] = tmpv
        else
            tmp[k] = v
        end
    end
    return tmp
end

local pi2, halfPi = math.pi * 2, math.pi / 2
local resetAngelRange = function(angle)
    if (math.abs(angle) > math.pi) then
        angle = math.abs(angle) >= pi2 and angle % pi2 or angle
        return -copysign(pi2 - math.abs(angle), angle)
    else
        return angle
    end
end

local resetAngelRangeDeg = function(angle)
    if (math.abs(angle) > 180) then
        angle = math.abs(angle) >= 360 and angle % 360 or angle
        return -copysign(360 - math.abs(angle), angle)
    else
        return angle
    end
end

local vector = {}
local newVec = function (x, y, z)
    if type(x) == "table" then
        return setmetatable({ x = x.x, y = x.y, z = x.z}, { __index = vector })
    elseif x and y and z then
        return setmetatable({ x = x, y = y, z = z}, { __index = vector})
    else
        return setmetatable({ x = 0, y = 0, z = 0}, { __index = vector})
    end
end

function vector:zero()
    self.x = 0
    self.y = 0
    self.z = 0
    return self
end

function vector:copy()
    return newVec(self.x, self.y, self.z)
end

function vector:len()
    return math.sqrt(self.x ^ 2 + self.y ^ 2 + self.z ^ 2)
end

function vector:norm()
    local l = self:len()
    if l == 0 then
        self:zero()
    else
        self.x = self.x / l
        self.y = self.y / l
        self.z = self.z / l
    end
    return self
end

function vector:nega()
    self.x = -self.x
    self.y = -self.y
    self.z = -self.z
    return self
end

function vector:add(v)
    self.x = self.x + v.x
    self.y = self.y + v.y
    self.z = self.z + v.z
    return self
end

function vector:sub(v)
    self.x = self.x - v.x
    self.y = self.y - v.y
    self.z = self.z - v.z
    return self
end

function vector:scale(num)
    self.x = self.x * num
    self.y = self.y * num
    self.z = self.z * num
    return self
end

function vector:unpack()
    return self.x, self.y, self.z
end

local unpackVec = function(v)
    return v.x, v.y, v.z
end

local vector2d = {}
function vector2d:len()
    return math.sqrt(self.x ^ 2 + self.y ^ 2)
end

function vector2d:norm()
    local l = self:len()
    if l == 0 then
        self:zero()
    else
        self.x = self.x / l
        self.y = self.y / l
    end
    return self
end

function vector2d:scale(num)
    self.x = self.x * num
    self.y = self.y * num
    return self
end

function vector2d:scaleVec(v)
    self.x = self.x * v.x
    self.y = self.y * v.y
    return self
end

function vector2d:add(v)
    self.x = self.x + v.x
    self.y = self.y + v.y
    return self
end

function vector2d:sub(v)
    self.x = self.x - v.x
    self.y = self.y - v.y
    return self
end

function vector2d:zero()
    self.x = 0
    self.y = 0
    return self
end


local new2dVec = function (x, y)
    if type(x) == "table" then
        return setmetatable({x = x.x, y = x.y}, { __index = vector2d })
    elseif x and y then
        return setmetatable({x = x, y = y}, {__index = vector2d})
    else
        return setmetatable({x = 0, y = 0}, {__index = vector2d})
    end
end

function vector2d:copy()
    return new2dVec(self.x, self.y)
end

local matrixMultiplication = function(m, v)
    return new2dVec(m[1][1] * v.x + m[1][2] * v.y, m[2][1] * v.x + m[2][2] * v.y)
end

local matrixMultiplication_3d = function (m, v)
    return newVec(
        m[1][1] * v.x + m[1][2] * v.y + m[1][3] * v.z,
        m[2][1] * v.x + m[2][2] * v.y + m[2][3] * v.z,
        m[3][1] * v.x + m[3][2] * v.y + m[3][3] * v.z
    )
end

local blockOffset = newVec(0.5, 0.5, 0.5)
----------------------------------------------------
system = {
    files = {
        propFileName = "dat",
        holograms = "holograms",
    },
    file = nil
}

function system:init()
    properties = system.datFromFile(self.files.propFileName)
    hologram_prop = system.datFromFile(self.files.holograms)
    local faceVec = engine_controller.getFaceRaw()
    if faceVec.x == 1 then
        properties.shipFace = "东"
    elseif faceVec.x == -1 then
        properties.shipFace = "西"
    elseif faceVec.z == 1 then
        properties.shipFace = "南"
    else
        properties.shipFace = "北"
    end
    system:updatePersistentData()
end

system.datFromFile = function (fileName)
    local file = io.open(fileName, "r")
    local result
    if file then
        local tmpFile = textutils.unserialise(file:read("a"))
        
        if fileName == "dat" then
            result = system.resetProp()
            for k, v in pairs(result) do
                if tmpFile[k] then
                    result[k] = tmpFile[k]
                end
            end
        elseif fileName == "holograms" then
            result = {}
            for k, v in pairs(tmpFile) do
                result[k] = v
            end
        end

        file:close()
    else
        if fileName == "dat" then
            result = system.resetProp()
        elseif fileName == "holograms" then
            result = {}
        end
    end
    
    return result
end

system.resetProp = function()
    local firstMonitor = peripheral.find("monitor")
    local enabledMonitors = { "computer" }
    if firstMonitor then
        table.insert(enabledMonitors, peripheral.getName(firstMonitor))
    end
    return {
        userName = "法师大人",
        holo_eye_pos = newVec(-3, 0, 0),
        mode = 1,
        HOME = { x = 0, y = 120, z = 0 },
        homeList = {
            { x = 0, y = 120, z = 0 }
        },
        enabledMonitors = enabledMonitors,
        winIndex = {},
        profileIndex = "键盘",
        coupled = true,
        drawHoloBorder = true,
        autoReplay = false,
        radarMode = 1,
        radarFov = 90,
        radarRange = 2048,
        radar_lock_mode = true,
        language = language[1],
        password = "123456",
        whiteList = {},
        shipNet_whiteList = {},
        spaceShipThrottle = 3,
        lastParent = -1,
        canTeleport = false,
        pathRange = 0,
        pathFollowMode = 1,
        profile = {
            键盘 = {
                spaceShip_P = 1.2,
                spaceShip_D = 2.4,
                spaceShip_forward = 1.5,
                spaceShip_sideMove = 1.5,
                spaceShip_vertMove = 1.5,
                spaceShip_burner = 3.0,
                spaceShip_move_D = 1,
                roll_rc_rate = 1,
                roll_s_rate = 0.7,
                roll_expo = 0.3,
                yaw_rc_rate = 1,
                yaw_s_rate = 0.7,
                yaw_expo = 0.3,
                pitch_rc_rate = 1,
                pitch_s_rate = 0.7,
                pitch_expo = 0.3,
                max_throttle = 1.25,
                throttle_mid = 0.15,
                throttle_expo = 1.0,
                helicopt_ROT_P = 0.3,
                helicopt_ROT_D = 0.5,
                helicopt_MAX_ANGLE = 50,
                helicopt_ACC = 0.5,
                helicopt_ACC_D = 0.75,
                airShip_ROT_P = 0.5,
                airShip_ROT_D = 0.5,
                airShip_MOVE_P = 1,
                camera_rot_speed = 0.2,
                camera_move_speed = 0.2,
                shipFollow_move_speed = 0.2,
            },
            摇杆 = {
                spaceShip_P = 3,
                spaceShip_D = 6,
                spaceShip_forward = 2,
                spaceShip_sideMove = 2,
                spaceShip_vertMove = 2,
                spaceShip_burner = 3.0,
                spaceShip_move_D = 1.6,
                roll_rc_rate = 1,
                roll_s_rate = 0.7,
                roll_expo = 0.3,
                yaw_rc_rate = 1,
                yaw_s_rate = 0.7,
                yaw_expo = 0.3,
                pitch_rc_rate = 1,
                pitch_s_rate = 0.7,
                pitch_expo = 0.3,
                max_throttle = 1.25,
                throttle_mid = 0.15,
                throttle_expo = 1.0,
                helicopt_ROT_P = 0.3,
                helicopt_ROT_D = 0.5,
                helicopt_MAX_ANGLE = 50,
                helicopt_ACC = 0.5,
                helicopt_ACC_D = 0.75,
                airShip_ROT_P = 0.5,
                airShip_ROT_D = 0.5,
                airShip_MOVE_P = 1,
                camera_rot_speed = 1,
                camera_move_speed = 0.5,
                shipFollow_move_speed = 0.5,
            }
        },
        lock = false,
        zeroPoint = 0,
        gravity = -2,
        airMass = 2, --空气密度 (风阻)
        rayCasterRange = 128,
        shipFace = "南",
        bg = "f",
        font = "8",
        title = "3",
        select = "3",
        other = "7",
        MAX_MOVE_SPEED = 99,                    --自动驾驶最大跟随速度
        followRange = { x = -1, y = 0, z = 0 }, --跟随距离
        shipFollow_offset = { x = -3, y = 0, z = 0 },
        shipFollow_use_custom_rotation = false,
        shipFollow_rotation = { roll = 0, yaw = 0, pitch = 0 },
        pointList = {                           --点循环模式，按照顺序逐个前往
        },
        anchorage_offset = {
            x = -5,
            y = 0,
            z = 0
        },
        anchorage_entry = 1
    }
end

function system:updatePersistentData()
    system.write(self.files.propFileName, properties)
    system.write(self.files.holograms, hologram_prop)
end

system.write = function(f, obj)
    local file = io.open(f, "w")
    if file then
        file:write(textutils.serialise(obj))
        file:close()
    end
end

local quat = {}
function quat.new()
    return { w = 1, x = 0, y = 0, z = 0}
end

function quat.vecRot(q, v)
    local x = q.x * 2
    local y = q.y * 2
    local z = q.z * 2
    local xx = q.x * x
    local yy = q.y * y
    local zz = q.z * z
    local xy = q.x * y
    local xz = q.x * z
    local yz = q.y * z
    local wx = q.w * x
    local wy = q.w * y
    local wz = q.w * z
    local res = {}
    res.x = (1.0 - (yy + zz)) * v.x + (xy - wz) * v.y + (xz + wy) * v.z
    res.y = (xy + wz) * v.x + (1.0 - (xx + zz)) * v.y + (yz - wx) * v.z
    res.z = (xz - wy) * v.x + (yz + wx) * v.y + (1.0 - (xx + yy)) * v.z
    return newVec(res.x, res.y, res.z)
end

function quat.multiply(q1, q2)
    local newQuat = {}
    newQuat.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w
    newQuat.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x
    newQuat.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y
    newQuat.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z
    return newQuat
end

function quat.nega(q)
    return {
        w = q.w,
        x = -q.x,
        y = -q.y,
        z = -q.z,
    }
end

local newQuat = function(w, x, y, z)
    if type(w) == "table" then
        return setmetatable({ w = w.w, x = w.x, y = w.y, z = w.z}, { __index = quat })
    else
        return setmetatable({ w = w, x = x, y = y, z = z }, { __index = quat })
    end
end

local DEFAULT_PARENT_SHIP = {
    id = -1,
    name = "",
    pos = newVec(),
    rot = quat.new(),
    preQuat = quat.new(),
    velocity = newVec(),
    anchorage = { offset = newVec(), entry = "top" },
    size = engine_controller.getSize(),
    beat = beat_ct
}

local parentShip = DEFAULT_PARENT_SHIP

local sin2cos = function(s)
    return math.sqrt( 1 - s ^ 2)
end

local applyInvariantForce = function (x, y, z)
    flight_control.all_force = flight_control.all_force + x + y + z
    engine_controller.applyInvariantForce(x, y, z)
end

local applyRotDependentForce = function (x, y, z)
    flight_control.all_force = flight_control.all_force + x + y + z
    engine_controller.applyRotDependentForce(x, y, z)
end

local applyRotDependentTorque = function (x, y, z)
    flight_control.all_force = flight_control.all_force + x + y + z
    engine_controller.applyRotDependentTorque(x, y, z)
end

local absController = { hasUser = false }

function absController:refresh()
    self.hasUser = false
    if self.joy then
        if not pcall(self.joy.hasUser) then
            return
        end
        if self.joy.hasUser() then
            self.joy.setFullPrecision(true)
            self.hasUser = true
            self.LeftStick.x = -self.joy.getAxis(1)
            self.LeftStick.y = -self.joy.getAxis(2)
            self.RightStick.x = -self.joy.getAxis(3)
            self.RightStick.y = -self.joy.getAxis(4)
            self.A = self.joy.getButton(1)
            self.B = self.joy.getButton(2)
            self.X = self.joy.getButton(3)
            self.Y = self.joy.getButton(4)
            self.LB = self.joy.getButton(5)
            self.RB = self.joy.getButton(6)
            self.LT = self.joy.getAxis(5)
            self.RT = self.joy.getAxis(6)
            self.back = self.joy.getButton(7)
            self.start = self.joy.getButton(8)
            self.up = self.joy.getButton(12)
            self.down = self.joy.getButton(14)
            self.left = self.joy.getButton(15)
            self.right = self.joy.getButton(13)
            self.LeftJoyClick = self.joy.getButton(10)
            self.RightJoyClick = self.joy.getButton(11)
        end

        self.LB = self.LB and 1 or 0
        self.RB = self.RB and 1 or 0
        self.BTStick.x = self.LB - self.RB
        self.BTStick.y = self.LT - self.RT

    else
        self.joy.setFullPrecision(false)
        self:defaultOutput()
    end
end

local rotController = function(v, left)
    local ecFace = engine_controller.getFaceRaw()
    local matrix2d = {
        {-ecFace.x, ecFace.z},
        {-ecFace.z, -ecFace.x}
    }
    local matrix2dLeft = {
        {-ecFace.x, -ecFace.z},
        {ecFace.z, -ecFace.x}
    }
    if left then
        return matrixMultiplication(matrix2dLeft, v)
    else
        return matrixMultiplication(matrix2d, v)
    end
end

function absController:rot()
    self.BTStickRot = rotController(self.BTStick, true)
    self.RightStickRot = rotController(self.RightStick)
end

function absController:defaultOutput()
    self.LeftStick = new2dVec()
    self.RightStick = new2dVec()
    self.RightStickRot = new2dVec()
    self.BTStick = new2dVec()
    self.BTStickRot = new2dVec()
    self.LeftJoyClick = false
    self.RightJoyClick = false
    self.LB = 0
    self.RB = 0
    self.LT = 0
    self.RT = 0
    self.back = false
    self.start = false
    self.up = false
    self.down = false
    self.left = false
    self.right = false
    self.A = false
    self.B = false
    self.X = false
    self.Y = false
end

local defController = setmetatable({}, {__index = absController}):defaultOutput()

controllers = {
    controllers = {},
    activated = {}
}
function controllers:getAll()
    local allControllers = {peripheral.find("tweaked_controller")}
    for k, v in pairs(allControllers) do
        self.controllers[k] = setmetatable({ joy = v }, { __index = absController })
        self.controllers[k]:defaultOutput()
    end
end

local getUserByUUID = function(uuid)
    for k, v in pairs(scanner.players) do
        if v.uuid == uuid then
            return v.name
        end
    end
end

function controllers:run()
    while true do
        local flag = true
        if self.controllers then
            for k, v in pairs(self.controllers) do
                v:refresh()
                if v.hasUser then
                    v:rot()
                    if self.activated ~= v then
                        self.activated = v
                        properties.userName = getUserByUUID(v.joy.getUserUUID())
                        system:updatePersistentData()
                    end
                    flag = false
                    break
                end
            end
        end
        
        if flag then
            self.activated = defController
        end
        sleep(0.05)
    end
end

local teleport_ccvs = function(frame)
    ship.teleport({pos = frame.pos, rot = frame.rot})
end

local abs_recordings = { time = 0, recordings = {}, len = 0 }
function abs_recordings:play(index)
    local result = self.recordings[self.time]
    self.time = self.time + index
    if properties.autoReplay then
        if properties.canTeleport then
            if self.time > self.len then
                self.time = 1
                teleport_ccvs(self.recordings[self.time])
            elseif self.time < 1 then
                self.time = self.len
                teleport_ccvs(self.recordings[self.time])
            end
        else
            if self.time > self.len or self.time < 1 then
                self.time = self.time - index
                flight_control.replay_index = -flight_control.replay_index
            end
        end
        
    else
        if self.time > self.len or self.time < 1 then
            self.time = self.time > self.len and 1 or self.time < 1 and self.len or self.time
            flight_control.replay_index = 0
        end
    end
    return result
end

local new_recordings = function (name, obj)
    return setmetatable({ name = name, time = 1, recordings = obj, len = #obj, waiting = true }, { __index = abs_recordings })
end

local genParticle = function(x, y, z)
    commands.execAsync(string.format("particle minecraft:cloud %0.6f %0.6f %0.6f 0 0 0 0 0 force", x, y, z))
end

flight_control = {
    mass = 0,
    omega = newVec(),
    scale = 1,
    hold = false,
    pos = newVec(),
    pX = newVec(),
    pY = newVec(),
    pZ = newVec(),
    rot = quat.new(),
    rot_face = quat.new(),
    lastPos = newVec(),
    lastRot = quat.new(),
    lastForce = newVec(),
    q_yaw = quat.new(),
    lastYaw = 0,
    yaw = 0,
    roll = 0,
    pitch = 0,
    all_force = 0,
    y_point = newVec(0, 1, 0),
    rotMatrix_90 = {
        {0, -1}, {1, 0}
    },
    faceMatrix = {{0, 1},{-1, 0}},
    recordings = {},
    replay_index = 0,
    followPath = {},
    tmpp = 1 / (math.pi / 2)
}

local getWorldOffsetOfPcPos = function(v)
    local wPos = flight_control.pos:copy()
    local yardPos = newVec(engine_controller.getShipCenter())
    local selfPos = newVec(coordinate.getAbsoluteCoordinates())
    local offset = quat.vecRot(flight_control.rot, yardPos:sub(selfPos):sub(blockOffset):sub(v))
    return wPos:sub(offset)
end

function flight_control:send_to_childShips()
    local followPoint = flight_control.lastFollowPoint
    if #self.followPath > 0 then
        local prePos = newVec(self.followPath[1].pos)
        local len = prePos:sub(self.pos):len()
        local step = 0.025
        if len > step then
            table.insert(self.followPath, 1, { pos = newVec(self.pos), rot = newQuat(self.rot_face)})
        end

        --local range = (self.shipLength + properties.pathRange) * 10 
        --followPoint.pos = newVec(self.pX):nega():scale(self.shipLength + properties.pathRange):add(self.pos)
        --if #self.followPath > range then
        --    --commands.execAsync("say ".. #self.followPath)
        --    local lastPoint = self.followPath[range]
        --    if lastPoint then
        --        followPoint.rot = lastPoint.rot
        --        self.followPath[range] = nil
        --    end
        --end
        
        local range = (self.shipLength + properties.pathRange) * self.scale
        local range_step = range * 40
        if properties.pathFollowMode == 1 then
            if #self.followPath >= range_step then
                local lastPoint = self.followPath[#self.followPath]
                if lastPoint then
                    followPoint.pos = lastPoint.pos
                    followPoint.rot = lastPoint.rot
                end
                local len2 = #self.followPath - range_step
                for i = 1, len2, 1 do
                    self.followPath[#self.followPath] = nil
                end
            end
        else
            while true do
                local lastPoint = self.followPath[#self.followPath]
                if newVec(lastPoint.pos):sub(self.pos):len() > range then
                    followPoint = { pos = lastPoint.pos, rot = lastPoint.rot }
                    self.followPath[#self.followPath] = nil
                else
                    break
                end
            end
        end
        
        --commands.execAsync("say ".. #self.followPath)
    else
        table.insert(self.followPath, { pos = newVec(self.pos), rot = newQuat(self.rot_face)})
        followPoint = {
            pos = newVec(self.pX):nega():scale(self.shipLength):scale(self.scale):add(self.pos),
            rot = newQuat(self.rot_face),
        }
    end
    --genParticle(followPoint.pos.x, followPoint.pos.y, followPoint.pos.z)
    local endPos = newVec(self.pX):nega():scale(self.shipLength - 1 + properties.pathRange):scale(self.scale):add(self.pos)
    local anchorageWorldPos = getWorldOffsetOfPcPos(properties.anchorage_offset)
    local msg = {
        id = computerId,
        name = shipName,
        pos = newVec(self.pos),
        rot = newQuat(self.rot_face),
        roll = self.roll,
        preRot = newQuat(self.preRot),
        velocity = newVec(self.velocity),
        size = newVec(self.size),
        anchorage = { pos = anchorageWorldPos, entry = entryList[properties.anchorage_entry] },
        followPoint = followPoint,
        pathFollowMode = properties.pathFollowMode,
        scale = self.scale,
        endPos = endPos
    }

    if followPoint then
        for k, v in pairs(childShips) do
            msg.code = v.code
            rednet.send(v.id, msg, public_protocol)
        end
        flight_control.lastFollowPoint = followPoint
    end
end

function flight_control:pd_rot_control(vec, p, d)
    applyRotDependentTorque(vec:scale(p):sub(self.omega:scale(d)):scale(self.momentOfInertiaTensor[1][1]):unpack())
end

function flight_control:pd_mov_control(vec, p, d)
    applyRotDependentForce(vec:scale(p):sub(newVec(self.velocityRot):scale(d)):scale(self.mass):unpack())
end

function flight_control:pd_wolrd_space_control(vec, p, d)
    applyInvariantForce(vec:scale(p):sub(newVec(self.velocity):scale(d)):add(newVec(0, 10, 0)):scale(self.mass):unpack())
end

function flight_control:run(phy)
    if engineOff then return end
    self.all_force = 0
    local poseVel = phy.getShipPoseVel()
    local inertia = phy.getInertia()
    for k, v in pairs(poseVel) do
        self[k] = v
    end
    for k, v in pairs(inertia) do
        self[k] = v
    end

    local rowPoint = engine_controller.getFaceRaw()
    self.pX = quat.vecRot(self.rot, rowPoint)
    self.pY = quat.vecRot(self.rot, self.y_point)
    local m = self.rotMatrix_90
    self.pZ = {
        x = m[1][1] * rowPoint.x + m[1][2] * rowPoint.z,
        y = rowPoint.y,
        z = m[2][1] * rowPoint.x + m[2][2] * rowPoint.z,
    }
    self.pZ = quat.vecRot(self.rot, self.pZ)
    self.pRow = quat.vecRot(self.rot, newVec(-1, 0, 0))
    self.pZRow = quat.vecRot(self.rot, newVec(0, 0, -1))

    self.faceMatrix = {
        {rowPoint.x, rowPoint.z},
        {-rowPoint.z, rowPoint.x}
    }

    self.faceMatrix3d = {
        {rowPoint.x, 0, rowPoint.z},
        {0, 1, 0},
        {-rowPoint.z, 0, rowPoint.x},
    }

    self.yaw = -math.deg(math.atan2(self.pX.x, self.pX.z))
    self.pitch = math.deg(math.asin(self.pX.y))
    self.roll = math.deg(math.asin(self.pZ.y))
    self.pitch = self.pY.y > 0 and self.pitch or copysign(180 - math.abs(self.pitch), self.pitch)
    self.roll = self.pY.y > 0 and self.roll or copysign(180 - math.abs(self.roll), self.roll)
    --commands.execAsync(("say %d"):format(self.yaw))

    local yaw_rot = math.atan2(rowPoint.z, -rowPoint.x) / 2
    self.q_yaw = {
        w = math.cos(yaw_rot),
        x = 0,
        y = math.sin(yaw_rot),
        z = 0,
    }

    self.rot_face = quat.multiply(self.rot, self.q_yaw)

    local rot_nega = quat.nega(self.rot)
    self.pos = newVec(poseVel.pos)
    self.velocity = newVec(poseVel.velocity)
    self.velocityRot = quat.vecRot(rot_nega, self.velocity)
    self.omega_raw = self.omega
    self.omega = quat.vecRot(rot_nega, self.omega)
    self.size = engine_controller.getSize()
    self.sizeRot = matrixMultiplication_3d(self.faceMatrix3d, self.size)
    self.shipLength = math.abs(self.sizeRot.x)
    self.scale = ship.getScale().x

    self.speed = self.velocity:len()

    if self.recordings and self.recordings.play then
        local frame
        if self.recordings.waiting then
            frame = self.recordings.recordings[1]
            local err = newVec(frame.pos):sub(self.pos)
            if math.abs(err.x) + math.abs(err.y) + math.abs(err.z) < 0.025 then
                self.recordings.waiting = false
            end
        else
            frame = self.recordings:play(self.replay_index)
        end
        --local pos = frame.pos
        --if commands then
        --    genParticle(pos.x, pos.y, pos.z)
        --end
        self:gotoPos_PD(frame.pos, 18, 20)
        self:gotoRot_PD(quat.multiply(frame.rot, quat.nega(self.q_yaw)), 7, 30)
        if controllers.activated then
            self.recordings = {}
        end
    else
        if modelist[properties.mode].name == "SpaceShip" then
            self:spaceShip()
        elseif modelist[properties.mode].name == "QuadFPV" then
            self:fpv()
        elseif modelist[properties.mode].name == "Helicopter" then
            self:helicopter()
        elseif modelist[properties.mode].name == "AirShip" then
            self:airShip()
        elseif modelist[properties.mode].name == "Hms_Fly" then
            self:hms_fly()
        elseif modelist[properties.mode].name == "PathFollow"
        or modelist[properties.mode].name == "ShipCamera"
        or modelist[properties.mode].name == "ShipFollow" then
            if parentShip.id ~= -1 then
                if modelist[properties.mode].name == "PathFollow" then
                    self:PathFollow()
                elseif modelist[properties.mode].name == "ShipCamera" then
                    self:shipCamera()
                else
                    self:shipFollow()
                end
            else
                self:spaceShip()
            end
        end
        replay_listener:run()
    end
    
    if #childShips > 0 then
        self:send_to_childShips()
    end
end

function flight_control:getCtAndProfile()
    return controllers.activated, properties.profile[properties.profileIndex]
end

local press_ct_1 = 0
function flight_control:spaceShip()
    dimension = coordinate.getSelfDimensionType()
    local movFor, rotFor = newVec(), newVec()
    local ct, profile = self:getCtAndProfile()
    --self:gotoPos(self.lastPos)
    --self:gotoRot(self:genRotByEuler(0, 0, math.rad(120)))
    
    if properties.lock then
        self:gotoPos(self.lastPos)
        self:gotoRot(self.lastRot)
    else
        if ct then
            if flight_control.hold and press_ct_1 < 1 and (
                math.abs(ct.BTStick.y) > 0.2 or
                math.abs(ct.LeftStick.x) > 0.2 or
                math.abs(ct.RightStick.x) > 0.2 or
                math.abs(ct.RightStick.y) > 0.2) then
                flight_control.hold = false
            end

            if ct.start and press_ct_1 < 1 then
                flight_control.hold = not flight_control.hold
                flight_control:setLastPos()
                press_ct_1 = 30
            end
        end

        if flight_control.hold then
            local rot = self:genRotByEuler(0, self.lastYaw, 0)
            local mov = newVec(self.lastForce.x, self.lastForce.y, self.lastForce.z)
            if dimension ~= "solar_system" then
                mov:add(quat.vecRot(quat.nega(self.rot), newVec(0, 10, 0)))
            end
            self:pd_mov_control(mov, 1, profile.spaceShip_move_D)
            self:gotoRot_PD(rot, 0.3, 9)
        else
            if ct then
                local throttle_level = properties.spaceShipThrottle * 0.33 + 0.01
                local PD_FROM_PROFILE = rotController(new2dVec(profile.spaceShip_forward, profile.spaceShip_sideMove))
                movFor.x = math.deg(math.asin(ct.BTStickRot.y)) * math.abs(PD_FROM_PROFILE.x) * throttle_level
                movFor.y = math.deg(math.asin(ct.LeftStick.y)) * profile.spaceShip_vertMove * throttle_level
                movFor.z = math.deg(math.asin(ct.BTStickRot.x)) * math.abs(PD_FROM_PROFILE.y) * throttle_level
                movFor:scale(0.5)
                if ct.LeftJoyClick then
                    movFor:scale(profile.spaceShip_burner)
                end
                if ct.RightJoyClick and press_ct_1 < 1 then
                    properties.coupled = not properties.coupled
                    press_ct_1 = 30
                end
                if (ct.up or ct.down) and press_ct_1 < 1 then
                    if ct.up then
                         properties.spaceShipThrottle = properties.spaceShipThrottle + 1
                         properties.spaceShipThrottle = properties.spaceShipThrottle > 9 and 9 or properties.spaceShipThrottle
                    else
                        properties.spaceShipThrottle = properties.spaceShipThrottle - 1
                        properties.spaceShipThrottle = properties.spaceShipThrottle < 1 and 1 or properties.spaceShipThrottle
                    end
                    press_ct_1 = 10
                end
        
                rotFor.x = math.asin(ct.RightStickRot.x)
                rotFor.y = math.asin(ct.LeftStick.x)
                rotFor.z = math.asin(ct.RightStickRot.y)
                rotFor:scale(5)

                self.lastForce = movFor:copy()
            end
    
            if properties.coupled then
                if dimension ~= "solar_system" then
                    movFor:add(quat.vecRot(quat.nega(self.rot), newVec(0, 10, 0)))
                end
                self:pd_mov_control(movFor:copy(), 1, profile.spaceShip_move_D)
            else
                self:pd_mov_control(movFor:copy(), 1, 0.2)
            end

            self:pd_rot_control(rotFor, profile.spaceShip_P, profile.spaceShip_D)
        end
        if press_ct_1 > 0 then
            press_ct_1 = press_ct_1 - 1
        end
    end
end

local getRate = function(rc, s, exp, x)
    if s >= 1 then
        s = 0.99
    end
    local flag = x < 0 and true or false
    x = math.abs(x)
    local p = 1 / (1 - (x * s))
    local q = (math.pow(x, 4) * exp) + x * (1 - exp)
    local r = 200 * q * rc
    local t = r * p
    return flag and -t or t
end

local getFpvThrottle = function(mid, t_exp, x)
    x = x > 1 and 1 or x
    local flag = x < 0 and true or false
    x = math.abs(x)
    local result = 0
    if x < mid then
        x = 1 - (x / mid)
        result = (math.pow(x, 2) * t_exp) + x * (1 - t_exp)
        result = mid - result * mid
    else
        x = (x - mid) / (1 - mid)
        result = (math.pow(x, 2) * t_exp) + x * (1 - t_exp)
        result = mid + result * (1 - mid)
    end
    return flag and -result or result
end

function flight_control:fpv()
    local ct, profile = self:getCtAndProfile()

    local velocity_tick = self.velocity:copy():scale(0.01666666666666666666666666666667)
    local damping = newVec(velocity_tick.x ^ 2, velocity_tick.y ^ 2, velocity_tick.z ^ 2)
    damping.x = copysign(damping.x, -velocity_tick.x)
    damping.y = copysign(damping.y, -velocity_tick.y)
    damping.z = copysign(damping.z, -velocity_tick.z)
    damping:scale(30):scale(properties.airMass):scale(self.mass)

    local movFor = newVec()
    if ct then
        local rotFor = newVec(
                math.deg(math.asin(ct.RightStickRot.x)),
                math.deg(math.asin(ct.LeftStick.x)),
                -math.deg(math.asin(ct.RightStickRot.y))
            )
        if properties.lock then
            local yf = math.deg(math.asin(ct.LeftStick.y)) + 10
            movFor.y = yf / self.pY.y
            movFor.y = movFor.y == math.huge and yf or movFor.y
            movFor.y = movFor.y - self.velocity.y

            local len2 = math.sqrt(rotFor.x ^ 2 + rotFor.z ^ 2)
            if len2 > 60 then
                rotFor.x = rotFor.x / len2 * 60
                rotFor.z = rotFor.z / len2 * 60
            end
    
            rotFor.x = (rotFor.x - self.roll)
            rotFor.z = -(rotFor.z - self.pitch)

            applyRotDependentTorque(rotFor:scale(3):sub(self.omega:scale(30)):scale(self.momentOfInertiaTensor[1][1]):unpack())
        else
            local throttle
            if properties.zeroPoint == -1 then
                throttle = math.asin((ct.LeftStick.y + 1) / 2) * self.tmpp
            else
                throttle = math.asin(ct.LeftStick.y) * self.tmpp
            end
            throttle = getFpvThrottle(profile.throttle_mid, profile.throttle_expo, throttle) * 2 * profile.max_throttle
            movFor.y = math.deg(throttle)
            
            rotFor:scale(self.tmpp)
            rotFor.x = getRate(profile.roll_rc_rate, profile.roll_s_rate, profile.roll_expo, ct.RightStickRot.x)
            rotFor.y = getRate(profile.yaw_rc_rate, profile.yaw_s_rate, profile.yaw_expo, ct.LeftStick.x)
            rotFor.z = getRate(profile.pitch_rc_rate, profile.pitch_s_rate, profile.pitch_expo, ct.RightStickRot.y)
        
            damping.y = damping.y + (1 + properties.gravity) * 10 * self.mass --重力附加
            applyRotDependentTorque(rotFor:scale(0.5239):sub(self.omega:scale(30)):scale(self.momentOfInertiaTensor[1][1]):unpack())
        end
    else
        local pp = math.abs(self.pY.y)
        pp = pp > 0.5 and pp or 0.5
        movFor.y = 10 / pp
        movFor.y = movFor.y == math.huge and 10 or movFor.y
        movFor.y = movFor.y - self.velocityRot.y

        local newVel = self.velocityRot
        local rotFor = newVec(-newVel.z, 0, newVel.x)
        rotFor:scale(9)

        rotFor.x = rotFor.x - math.deg(math.asin(self.pZRow.y))
        rotFor.z = rotFor.z + math.deg(math.asin(self.pRow.y))
        rotFor.x = math.abs(rotFor.x) > 90 and copysign(90, rotFor.x) or rotFor.x
        rotFor.z = math.abs(rotFor.z) > 90 and copysign(90, rotFor.z) or rotFor.z

        applyRotDependentTorque(rotFor:scale(2):sub(self.omega:scale(30)):scale(self.momentOfInertiaTensor[1][1]):unpack())
    end

    damping = quat.vecRot(quat.nega(self.rot), damping)
    applyRotDependentForce(movFor:scale(self.mass):add(damping):unpack())
end

function flight_control:helicopter()
    local ct, profile = self:getCtAndProfile()

    local movFor = newVec()
    local rot
    local localYaw = math.atan2(self.pRow.z, self.pRow.x)
    if ct then
        local max_ag = math.rad(profile.helicopt_MAX_ANGLE) * 2 / math.pi
        rot = self:genRotByEuler(
            math.asin(ct.RightStickRot.x * max_ag),
            resetAngelRange(localYaw - math.asin(ct.LeftStick.x) / 2),
            -math.asin(ct.RightStickRot.y * max_ag)
        )
        movFor.y = math.deg(math.asin(ct.LeftStick.y)) / 4 * profile.helicopt_ACC + -flight_control.velocityRot.y * profile.helicopt_ACC_D
    else
        rot = self:genRotByEuler(0, localYaw, 0)
    end
    self:gotoRot_PD(rot, profile.helicopt_ROT_P, profile.helicopt_ROT_D * 10)
    movFor.y = movFor.y + 10
    self:pd_mov_control(movFor, 1, 0.05)
end

function flight_control:airShip()
    local ct, profile = self:getCtAndProfile()
    local movFor = newVec()
    local rot
    local localYaw = math.atan2(self.pRow.z, self.pRow.x)
    if ct then
        rot = self:genRotByEuler(0, resetAngelRange(localYaw - math.asin(ct.LeftStick.x) / 32), 0)
        movFor.x = math.deg(math.asin(ct.BTStickRot.y)) / 2 * profile.helicopt_ACC + -flight_control.velocityRot.x * profile.helicopt_ACC_D
        movFor.y = math.deg(math.asin(ct.LeftStick.y)) / 4 * profile.airShip_MOVE_P + -flight_control.velocityRot.y
        movFor.z = math.deg(math.asin(ct.BTStickRot.x)) / 2 * profile.helicopt_ACC + -flight_control.velocityRot.z * profile.helicopt_ACC_D
    else
        rot = self:genRotByEuler(0, localYaw, 0)
    end
    self:gotoRot_PD(rot, profile.airShip_ROT_P, profile.airShip_ROT_D * 5)
    movFor.y = movFor.y + 10
    self:pd_mov_control(movFor, 1, 1)
end

local cameraQuat = quat.new()
local xOffset = 0
function flight_control:shipCamera()
    local ct = controllers.activated
    local profile = properties.profile[properties.profileIndex]

    local pos = newVec(parentShip.pos):add(newVec(parentShip.velocity):scale(0.05))
    local defSize = math.max(math.max(parentShip.size.x, parentShip.size.z), parentShip.size.y) * parentShip.scale
    local minSize = math.min(math.min(parentShip.size.x, parentShip.size.z), parentShip.size.y) * parentShip.scale
    local range = newVec(defSize + xOffset, 0, 0)

    if ct then
        xOffset = xOffset + math.asin(ct.BTStick.y) * profile.camera_move_speed
        xOffset = xOffset < minSize and minSize or xOffset
        xOffset = xOffset > 128 and 128 or xOffset
        range = newVec(minSize + xOffset, 0, 0)

        local tmp_d = 0.01 * xOffset
        local rotSpeedWithDis = profile.camera_rot_speed / ( 1 + tmp_d )
        local myRot = newVec(
            math.asin(ct.RightStick.x) * profile.camera_rot_speed,
            math.asin(ct.LeftStick.x) * rotSpeedWithDis,
            math.asin(ct.LeftStick.y) * rotSpeedWithDis
        )

        myRot:scale(0.05)
        local x_rot = myRot.x / 2
        local qx = {
            w = math.cos(x_rot),
            x = math.sin(x_rot),
            y = 0,
            z = 0
        }
        local y_rot = -myRot.y / 2
        local qy = {
            w = math.cos(y_rot),
            x = 0,
            y = math.sin(y_rot),
            z = 0
        }
        local z_rot = myRot.z / 2
        local qz = {
            w = math.cos(z_rot),
            x = 0,
            y = 0,
            z = math.sin(z_rot)
        }

        local q_rot = quat.multiply(quat.multiply(qx, qy), qz)
        cameraQuat = quat.multiply(cameraQuat, q_rot)
    end
    range = quat.vecRot(cameraQuat, range)
    pos = pos:add(range)
    self:gotoRot_PD(quat.multiply(cameraQuat, quat.nega(self.q_yaw)), 2, 24)
    self:gotoPos_PD(pos, 6, 18)
end

function flight_control:PathFollow()
    local frame = parentShip.followPoint
    if frame then
        if properties.pathFollowMode == 1 then
            self:gotoRot_PD(quat.multiply(frame.rot, quat.nega(self.q_yaw)), 8, 32)
        else
            local errPos = newVec(parentShip.pos):sub(self.pos):norm()
            --errPos = matrixMultiplication_3d(self.faceMatrix3d, errPos):norm()
            errPos = quat.vecRot(quat.nega(parentShip.rot), errPos)
            local eYaw = math.atan2(errPos.z, errPos.x)
            local ePitch = math.asin(errPos.y)
            local rot = self:genRotByEuler(0, eYaw, ePitch)
            self:gotoRot_PD(quat.multiply(quat.multiply(parentShip.rot, rot), quat.nega(self.q_yaw)), 8, 32)
        end
        self:gotoPos_PD(frame.pos, 20, 18)
    else
        self:spaceShip()
    end
end

function flight_control:shipFollow()
    local pos = newVec(parentShip.pos):add(newVec(parentShip.velocity):scale(0.05))
    local offset = newVec(properties.shipFollow_offset)
    offset.x = offset.x + parentShip.size.x + flight_control.size.x

    local parentQ = quat.multiply(parentShip.rot, quat.nega(self.q_yaw))
    offset = quat.vecRot(parentQ, offset)
    pos = pos:add(offset)
    self:gotoPos_PD(pos, 18, 20)

    if properties.shipFollow_use_custom_rotation then
        local localRot = self:genRotByEuler(
            -math.rad(properties.shipFollow_rotation.roll),
            resetAngelRange(math.rad(properties.shipFollow_rotation.yaw) + math.pi),
            -math.rad(properties.shipFollow_rotation.pitch)
        )
        parentQ = quat.multiply(parentQ, localRot)
    end
    self:gotoRot_PD(parentQ, 7, 30)
end

function flight_control:gotoPos(pos)
    self:gotoPos_PD(pos, 1, 6)
end

function flight_control:gotoPos_PD(pos, p, d)
    local tg = newVec(pos):sub(self.pos)
    tg = tg:len() > 299 and tg:norm():scale(299) or tg
    self:pd_wolrd_space_control(tg:scale(10), p, d)
end

function flight_control:gotoRot(rot)
    self:gotoRot_PD(rot, 1, 18)
end

function flight_control:gotoRot_PD(rot, p, d)
    local selfRot
    selfRot = newQuat(self.rot.w, self.rot.x, self.rot.y, self.rot.z)
    local xp, zp = quat.vecRot(selfRot, newVec(1, 0, 0)), quat.vecRot(selfRot, newVec(0, 0, 1))
    xp = quat.vecRot(quat.nega(rot), xp)
    zp = quat.vecRot(quat.nega(rot), zp)
    local xRot = math.deg(math.asin(zp.y))
    local yRot = math.deg(math.atan(xp.z, xp.x))
    local zRot = -math.deg(math.asin(xp.y))
    self:pd_rot_control(newVec(xRot, yRot, zRot), p, d)
end

function flight_control:genRotByEuler(roll, yaw, pitch)
    local cosp = math.abs(math.cos(pitch))
    local cosr = math.abs(math.cos(roll))
    local xp = newVec(-math.cos(yaw) * cosp, math.sin(pitch), math.sin(yaw) * cosp)
    local zp = newVec(-math.sin(yaw) * cosr, math.sin(roll), -math.cos(yaw) * cosr)

    --commands.execAsync(("say %.2f %.2f %.2f"):format(xp.x, xp.y, xp.z))
    --commands.execAsync(("say %.2f %.2f %.2f"):format(zp.x, zp.y, zp.z))

    local pp = -math.asin(xp.y)
    local rr = math.asin(zp.y)
    if math.abs(roll) > halfPi then
        rr = copysign(math.pi - math.abs(rr), rr)
    end
    if math.abs(pitch) > halfPi then
        pp = copysign(math.pi - math.abs(pp), pp)
    end
    local halfR = rr / 2
    local xRot = newQuat(math.cos(halfR), math.sin(halfR), 0, 0)
    local halfY = math.atan2(xp.z, xp.x) / 2
    local yRot = newQuat(math.cos(halfY), 0, math.sin(halfY), 0)
    local halfP = pp / 2
    local zRot = newQuat(math.cos(halfP), 0, 0, math.sin(halfP))
    return quat.multiply(quat.multiply(yRot, xRot), zRot)
end

function flight_control:setLastPos()
    self.lastPos = self.pos
    self.lastRot = self.rot
    self.lastYaw = math.atan2(self.pRow.z, self.pRow.x)
end
--------------------------------------------------

scanner = {
    mobs = {},
    preMobs = {},
    vsShips = {},
    monsters = {},
    players = {},
    commander = {},
    preMonster = {},
    preplayers = {},
}

function scanner:getPlayer(range)
    self.players = coordinate.getPlayers(range)

    for k, v in pairs(self.preplayers) do
        v.flag = false
    end

    if self.players ~= nil then
        for k, v in pairs(self.players) do
            if scanner.preplayers[k] then
                v.velocity = {
                    x = v.x - scanner.preplayers[v.uuid].x,
                    y = v.y - scanner.preplayers[v.uuid].y,
                    z = v.z - scanner.preplayers[v.uuid].z
                }
            else
                v.velocity = newVec()
            end
            v.flag = true
            scanner.preplayers[v.uuid] = v
        end
    end

    for k, v in pairs(self.preplayers) do
        if not v.flag then
            self.preplayers[k] = nil
        end
    end

    return self.players
end

function scanner:getMobs(scope)
    self.mobs = coordinate.getMobs(scope)

    for k, v in pairs(self.preMobs) do
        v.flag = false
    end

    scanner.monsters = {}
    if scanner.mobs ~= nil then
        for k, v in pairs(scanner.mobs) do
            if scanner.preMobs[k] then
                v.velocity = {
                    x = v.x - scanner.preMobs[v.uuid].x,
                    y = v.y - scanner.preMobs[v.uuid].y,
                    z = v.z - scanner.preMobs[v.uuid].z
                }
            else
                v.velocity = newVec()
            end

            if v.name == "monster" then
                scanner.monsters[v.uuid] = v
            end

            v.flag = true
            scanner.preMobs[v.uuid] = v
        end
    end

    for k, v in pairs(self.preMobs) do
        if not v.flag then
            self.preMobs[k] = nil
        end
    end

    return self.mobs, self.monsters
end

function scanner:getCommander()
    self:getPlayer()
    for k, v in pairs(self.players) do
        if v.name == properties.userName then
            self.commander = v
            return self.commander
        end
    end
end

function scanner:get_commander_eye_pos()
    if self.commander then
        if self.commander.isPassenger then
            local eyePos = newVec(self.commander):add(quat.vecRot(flight_control.rot, newVec(0, self.commander.eyeHeight, 0)))
            self.commander.x = eyePos.x
            self.commander.y = eyePos.y
            self.commander.z = eyePos.z
        else
            self.commander.y = self.commander.y + self.commander.eyeHeight
        end
    end
end

function scanner:getShips(range)
    if not coordinate then
        return
    end
    local ships
    if dimension == "overworld" then
        ships = coordinate.getShips(range)
    else
        ships = coordinate.getShipsAll(range)
    end

    for k, v in pairs(scanner.vsShips) do
        v.flag = false
    end

    for k, v in pairs(ships) do
        if scanner.vsShips[v.id] then
            v.velocity = {
                x = v.x - scanner.vsShips[v.id].x,
                y = v.y - scanner.vsShips[v.id].y,
                z = v.z - scanner.vsShips[v.id].z
            }
        else
            v.velocity = {
                x = 0,
                y = 0,
                z = 0
            }
        end
        v.flag = true
        v.name = v.slug
        scanner.vsShips[v.id] = v
    end

    for k, v in pairs(scanner.vsShips) do
        if not v.flag then
            scanner.vsShips[k] = nil
        end
    end
    return scanner.vsShips
end

local max_fov_holo = {fov = 0}
radar = { targets = {}, final_targets = {}, other_targets = {} }
function radar:run()
    local press_ct = 0
    while true do
        if max_fov_holo.name then
            local fire, missile = false, false
            local ct = controllers.activated
            
            if ct then
                if (ct.left or ct.right or ct.Y) and press_ct < 1 then
                    if ct.left then
                        properties.radarMode = properties.radarMode - 1
                        properties.radarMode = properties.radarMode < 1 and 5 or properties.radarMode
                    elseif ct.right then
                        properties.radarMode = properties.radarMode + 1
                        properties.radarMode = properties.radarMode > 5 and 1 or properties.radarMode
                    elseif ct.Y then
                        properties.radar_lock_mode = not properties.radar_lock_mode
                    end
                    press_ct = 10
                    system:updatePersistentData()
                elseif ct.A or ct.B then
                    fire = ct.A and true or false
                    missile = ct.B and true or false
                elseif ct.back and press_ct < 1 then
                    for k, v in pairs(hologram_manager.holograms) do
                        v.screen.SetClearColor(0xFF1111FF)
                        v.screen.Clear()
                        v.screen.Flush(true)
                        v.screen.SetClearColor(0x00000000)
                    end
                    press_ct = 10
                end
            end

            if press_ct > 0 then
                press_ct = press_ct - 1
            end

            local isShip = false
            local targets = {}
            if properties.radarMode == 1 then --nil
                targets = nil
            elseif properties.radarMode == 2 then --vs_ship
                isShip = true
                targets = scanner:getShips(properties.radarRange)
            elseif properties.radarMode == 3 then --monster
                _, targets = scanner:getMobs(properties.radarRange)
            elseif properties.radarMode == 4 then --player
                targets = scanner:getPlayer(properties.radarRange)
            elseif properties.radarMode == 5 then --mobs
                targets, _ = scanner:getMobs(properties.radarRange)
            end

            self.targets = {}
            if targets then
                local tan = math.tan(max_fov_holo.fov)
                local count = 0
                for k, v in pairs(targets) do
                    local flag = true
                    if isShip then
                        for __, slug in pairs(properties.whiteList) do
                            if v.slug == slug then
                                flag = false
                            end
                        end
                    end

                    if flag then
                        local pos = max_fov_holo:offset_from_self(newVec(v)):sub(max_fov_holo.eye_offset)
                        local max_d = pos.x * tan
                        if pos.x > 4 and math.abs(pos.z) < max_d and math.abs(pos.y) < max_d then
                            count = count + 1
                            v.id = v.id and v.id or v.uuid
                            v.center_dis = math.sqrt(pos.y ^ 2 + pos.z ^ 2) / pos.x
                            table.insert(self.targets, v)
                        end
                    end
                end

                table.sort(self.targets, function(a, b) return a.center_dis < b.center_dis end)
            else
                self.targets = nil
            end

            self.final_targets = {}
            local target_count = 0
            if properties.radar_lock_mode then
                target_count = 1
            else
                target_count = #linkedCannons
            end

            if self.targets and target_count > 0 then
                target_count = target_count < #self.targets and target_count or #self.targets
                for i = 1, target_count, 1 do
                    self.targets[i].y = self.targets[i].y
                    self.final_targets[i] = self.targets[i]
                end
            else
                self.final_targets = nil
            end

            if self.targets then
                self.other_targets = {}
                local startIndex = self.final_targets and #self.final_targets + 1 or 1
                for i = startIndex, #self.targets, 1 do
                    table.insert(self.other_targets, self.targets[i])
                end
            else
                self.other_targets = nil
            end

            if self.final_targets then
                local len = #self.final_targets
                for i = 1, #linkedCannons, 1 do
                    local index = i % len == 0 and len or i % len
                    local tg = self.final_targets[index]
                    if tg then
                        if linkedCannons[i].name == "cbc_missile" then
                            rednet.send(linkedCannons[i].id, {
                                tgPos = newVec(tg.x, tg.y, tg.z),
                                velocity = tg.velocity,
                                rot = flight_control.rot,
                                raw_face = engine_controller.getFaceRaw(),
                                pos = flight_control.pos,
                                omega = flight_control.omega_raw,
                                center_velocity = flight_control.velocity,
                                missile = missile,
                            }, missile_protocol)
                        else
                            rednet.send(linkedCannons[i].id, {
                                tgPos = newVec(tg.x, tg.y, tg.z),
                                velocity = tg.velocity,
                                mode = 3,
                                fire = fire,
                                rot = flight_control.rot,
                                raw_face = engine_controller.getFaceRaw(),
                                pos = flight_control.pos,
                                omega = flight_control.omega_raw,
                                center_velocity = flight_control.velocity,
                            }, protocol)
                        end
                    end
                end
            end

            sleep(0.05)
        else
            sleep(0.1)
        end
        
    end
end

--------------------------------------------------
replay_listener = { isRunning = false, fileDir = "nil", rec = nil, count = 0, timeFlag = 0, cd = 0, lastCD = 0}
function replay_listener:check()
    local disk = peripheral.find("drive")
    if disk then
        self.isRunning = not self.isRunning
        if self.isRunning then
            local date = os.date("%y%m%d_%H%M%S")
            self.fileDir = "/disk/recordings/" .. date
            disk.setDiskLabel(date)
            fs.makeDir(self.fileDir)
            self.timeFlag = os.epoch("local")
            self.cd = 0
            self.rec = {}
        else
            self:update()
        end
        
        self.count = 0
    end
end

function replay_listener:update()
    if fs.getFreeSpace(".") > 160000 then
        local fName = self.fileDir .. "/" .. os.date("%H%M%S") .. ".rec"
        system.write(fName, self.rec)
        self.rec = {}
    else
        self.isRunning = not self.isRunning
        self.count = 0
    end
end

function replay_listener:run()
    if not self.isRunning then
        return
    end
    if self.cd < 3 then
        self.cd = math.floor((os.epoch("local") - self.timeFlag) / 1000)
        if self.cd ~= self.lastCD then
            self:refreshMonitor()
        end
        self.lastCD = self.cd
    else
        if self.cd == 3 then
            self:refreshMonitor()
            self.cd = 4
        end
        table.insert(self.rec, { pos = flight_control.pos, rot = flight_control.rot_face })
        self.count = self.count + 1
    end
   
    if self.count >= 36000 then
        self:check()
        self:update()
        monitorUtil.refreshAll()
    elseif self.count > 1 and self.count % 300 == 0 then
        self:update()
    end
end

function replay_listener:refreshMonitor()
    for n, screen in pairs(monitorUtil.screens) do
        if screen.windows then
            for i = 1, #screen.windows, 1 do
                for j = 1, #screen.windows[i], 1 do
                    local page = properties.winIndex[n][i][j]
                    if page == 26 then
                        screen.windows[i][j][page]:refresh()
                    end
                end
            end
        end
    end
end

--------------------------------------------------
local jizhi_7x7_fonts = {
_4f60 = {
    0,1,0,1,0,0,0,0,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,0,0,0,1,0,1,1,1,0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,},
_597d = {
    0,0,1,0,1,1,1,0,0,1,0,0,0,1,1,1,1,1,0,1,0,0,1,0,1,1,1,1,0,1,1,0,0,1,0,0,0,1,1,0,1,0,1,1,0,0,1,1,0,},
_9ad8 = {
    0,0,0,1,0,0,0,1,1,1,1,1,1,1,0,0,1,0,1,0,0,0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0,1,0,1,1,0,1,1,1,0,1,},
_5ea6 = {
    0,0,0,0,1,0,0,0,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,1,1,1,1,1,0,1,0,1,0,1,0,0,1,0,0,1,0,0,1,0,1,1,0,1,1,},
_901f = {
    1,0,0,0,1,0,0,0,1,1,1,1,1,1,0,0,1,0,1,0,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,0,1,1,0,1,0,1,1,0,1,1,1,1,1,},
_7c73 = {
    0,1,0,1,0,1,0,0,0,1,1,1,0,0,1,1,1,1,1,1,1,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,1,0,1,1,0,0,0,1,0,0,0,},
_2f = {
    0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,},
_65e0 = {
    0,1,1,1,1,1,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,1,0,1,0,1,1,1,0,0,1,1,1,}, --无
_96f7 = {
   0,0,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,1,0,1,0,1,0,0,1,1,1,1,1,0,}, --雷
_8fbe = {
 1,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1,1,0,0,0,1,0,0,1,0,0,1,0,1,0,1,0,1,0,0,0,1,1,1,1,1,1,1,1,}, --达
_74e6 = {
    1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,1,1,1,1,0,0,1,0,0,0,1,0,0,1,0,1,0,1,0,0,1,0,0,0,1,0,0,1,1,1,0,1,1,}, --瓦
_5c14 = {
    0,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,1,0,1,0,1,0,0,1,0,0,1,0,0,1,1,0,0,0,}, --尔
_57fa = {
    0,0,1,0,1,0,0,0,1,1,1,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,0,1,0,0,1,1,1,0,0,}, --基
_91cc = {
    0,1,1,1,1,1,0,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,}, --里
_602a = {
    0,1,1,1,1,1,0,1,1,0,1,0,1,0,1,1,0,0,1,0,0,0,1,1,1,0,1,1,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,1,1,1,1,1,1,}, --怪
_7269 = {
    1,1,0,1,0,0,0,1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,0,1,1,1,1,1,0,1,0,1,1,0,1,1,0,1,0,1,0,1,0,1,0,1,1,}, --物
_73a9 = {
    0,0,0,1,1,1,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,1,0,0,1,0,1,0,1,0,1,1,0,1,0,1,0,0,0,1,0,0,1,1,}, --玩
_5bb6 = {
    0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1,1,0,1,1,0,1,}, --家
_751f = {
    0,1,0,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,1,1,1,1,1,1,1,}, --生
}

local my_5x5_letter = {
["_cross"] ={ 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,},
["_cross_box"] = { 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1,},
["_"] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,},
["-"] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,},
["/"] = { 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,},
["<"] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0,},
[">"] = { 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,},
["a"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,},
["b"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,},
["c"] = { 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1,},
["d"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,},
["e"] = { 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,},
["f"] = { 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,},
["g"] = { 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1,},
["h"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,},
["i"] = { 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1,},
["j"] = { 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,},
["k"] = { 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,},
["l"] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1,},
["m"] = { 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,},
["n"] = { 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1,},
["o"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,},
["p"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,},
["q"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1,},
["r"] = { 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,},
["s"] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,},
["t"] = { 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,},
["u"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0},
["v"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0,},
["w"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,},
["x"] = { 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1,},
["y"] = { 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,},
["z"] = { 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1,},
["0"] = { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0,},
["1"] = { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0,},
["2"] = { 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1,},
["3"] = { 0, 3, 3, 3, 0, 0, 0, 0, 0, 3, 0, 0, 3, 3, 3, 0, 0, 0, 0, 3, 0, 3, 3, 3, 0,},
["4"] = { 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,},
["5"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, },
["6"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, },
["7"] = { 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, },
["8"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, },
["9"] = { 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, },
}

local my_4x5_number = {
{0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,},
{0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0,},
{0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1,},
{1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,},
{1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,},
{1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,},
{1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,},
{1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0,},
{1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1,},
{1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1,}
}

local filterString = function(str)
    return string.gsub(str, "[^%w/%-_]", "")
end

local BakeBitMap = function(buf, color)
    local b = {}
    local i = 0
    for _, v in ipairs(buf) do
        if v > 0 then
            b[i] = color
        else
            b[i] = 0x00000000
        end
        i = i + 1
    end
    return b
end

for k, v in pairs(jizhi_7x7_fonts) do
    jizhi_7x7_fonts[k] = BakeBitMap(v, 0xFFFFFFFF)
end

local my_5x5_letter_white = {}
local my_5x5_letter_red = {}
for k, v in pairs(my_5x5_letter) do
    my_5x5_letter[k] = BakeBitMap(v, 0x33FFFFFF)
    my_5x5_letter_white[k] = BakeBitMap(v, 0xFFFFFFFF)
    my_5x5_letter_red[k] = BakeBitMap(v, 0xFF1111FF)
end

local my_4x5_number_white = {}
for k, v in pairs(my_4x5_number) do
    my_4x5_number[k] = BakeBitMap(v, 0x33FFFFFF)
    my_4x5_number_white[k] = BakeBitMap(v, 0xFFFFFFFF)
end

local absAttLine = {
    raw = {},
    list = {},
    move = {}
}

function absAttLine:transform(m, flag)
    self.list = {}
    for k, v in pairs(self.move) do
        self.list[k] = matrixMultiplication(m, v)
    end

    if flag then
        self:vertMir()
        for k, v in pairs(self.mir) do
            self.mir[k] = matrixMultiplication(m, v)
        end
    end
end

function absAttLine:mov(vec)
    for k, v in pairs(self.raw) do
        self.move[k] = new2dVec(v.x + vec.x, v.y + vec.y)
    end
end

function absAttLine:vertMir()
    self.mir = {}
    for k, v in pairs(self.move) do
        table.insert(self.mir, new2dVec(-v.x, v.y))
    end
end

local newAbsAttLine = function (t)
    return setmetatable({ raw = t, list = {}, move = {} }, { __index = absAttLine})
end

local abs2dVecGroup = { raw = {}, list = {} }
function abs2dVecGroup:transform(m)
    for k, v in pairs(self.list) do
       self.list[k] = matrixMultiplication(m ,v)
    end
end
local new2dVecGroup = function (t)
    return setmetatable({ raw = t, list = t }, { __index = abs2dVecGroup})
end
local new_lock_box = function ()
    return new2dVecGroup(
        {
            new2dVec(-0.2, -0.2),
            new2dVec(0.2, -0.2),
            new2dVec(0.2, 0.2),
            new2dVec(-0.2, 0.2),
            new2dVec(-0.2, -0.2)
        }
    )
end
local new_other_lock_box = function ()
    return new2dVecGroup(
        {
            new2dVec(-0.01, 0),
            new2dVec(0, -0.01),
            new2dVec(0.01, 0),
        }
    )
end
local cross_box = new2dVecGroup(
    {
        new2dVec(-0.01, -0.00866),
        new2dVec(0.01, -0.00866),
        new2dVec(0, 0.00866),
        new2dVec(-0.01, -0.00866),
    }
)
--------------------------------------------------
local absHoloGram = {}

function absHoloGram:initData()
    self.width = 384
    self.height = 256
    self.rotation = newVec()
    self.translation = newVec(0, 1, 0)
    self.scale = 2
    self.bg = {
        r = 0,
        g = 0,
        b = 0,
        a = 0
    }
    self.eye_offset = newVec(2, 0.12, 0)
    self.attBorder = new2dVec(0.1, 0.1)
    self.msg_bar_offset = 0.9
    self.cannon_bar_offset = 0.6
    self.target_bar_offset = 0.80
    self.attSize = 1
    self.lint_interval = 5
    self.drawHoloBorder = true
    self.drawInputLine = true
    self.rgb_lock_box = false
    self.other_targets = true
end

function absHoloGram:init()
    self:initData()
    self:checkProp()
    self.midPoint = new2dVec(self.width / 2, self.height / 2)
    self.heightPos = new2dVec(0.8, 0):scaleVec(self.midPoint):add(self.midPoint)
    self.hightFontPos = new2dVec(0.8, 0.05):scaleVec(self.midPoint):add(self.midPoint)
    self.speedPos = new2dVec(-0.9, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.speedFontPos = new2dVec(-0.75, self.msg_bar_offset-0.01):scaleVec(self.midPoint):add(self.midPoint)
    self.energyPos = new2dVec(0.85, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.energyFontPos = new2dVec(0.85, self.msg_bar_offset+0.05):scaleVec(self.midPoint):add(self.midPoint)
    self.massPos = new2dVec(0.85, self.msg_bar_offset-0.1):scaleVec(self.midPoint):add(self.midPoint)
    self.radarPos_left = new2dVec(-0.2, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.radarPos_right = new2dVec(0.2, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.radarModePos = new2dVec(0, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.eye_offset = newVec(self.eye_offset)
    self.eye_offset.x = -math.abs(self.eye_offset.x)
    self.eye_len = self.eye_offset:len()
    self.eye_pitch_offset = math.tan(math.asin(self.eye_offset.y / self.eye_len)) * self.eye_offset.x / 2 * self.midPoint.y / self.scale

    self.cannonCountPos = new2dVec(-0.9, self.cannon_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.targetPos = new2dVec(0, self.target_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    local per_pix_for_pers = 1 / self.midPoint.y
    local hp_h = copysign(math.abs(self.target_bar_offset) + 6 * per_pix_for_pers, self.target_bar_offset)
    self.hp_pos_start = new2dVec(-0.3, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    self.hp_pos_end = new2dVec(0.3, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    self.hp_bar_len = 0.6
    self.hp_text_start = new2dVec(-0.3 - 8 * per_pix_for_pers, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    self.hp_text_end = new2dVec(0.3 + 8 * per_pix_for_pers, hp_h):scaleVec(self.midPoint):add(self.midPoint)
    
    self.ThrottlePos = new2dVec(0.5, self.msg_bar_offset):scaleVec(self.midPoint):add(self.midPoint)
    self.ThrottleFontPos = new2dVec(0.5 + 20 * per_pix_for_pers, self.msg_bar_offset + 2 * per_pix_for_pers):scaleVec(self.midPoint):add(self.midPoint)
    self.holdingPos = new2dVec(-0.8, self.msg_bar_offset - 12 * per_pix_for_pers):scaleVec(self.midPoint):add(self.midPoint)

    self.borders = { full = new2dVec(0, 0), attBorder = self.attBorder }
    for k, v in pairs(self.borders) do
        self.borders[k] = new2dVec(v.x * self.width, v.y * self.height)
    end

    local bgColor = tonumber(string.format("%x%x%x%x", self.bg.r, self.bg.g, self.bg.b, self.bg.a), 16)
    self.screen.SetClearColor(bgColor)
    self.screen.Resize(self.width, self.height)
    local sc = (16 / self.width) * self.scale
    self.screen.SetScale(sc, sc)
    local translat_t = { x = -self.translation.z, y = self.translation.y, z = self.translation.x}
    self.screen.SetTranslation(unpackVec(translat_t))
    self.screen.SetRotation(unpackVec(self.rotation))

    self.fov = math.acos(math.abs(self.eye_offset.x) / math.sqrt(self.eye_offset.x ^ 2 + (self.scale * 0.5) ^ 2))
    if self.fov > max_fov_holo.fov then
        max_fov_holo = self
    end
    self.locked_list = {}
    self.other_list = {}

    self.attLines = {
        line_center = newAbsAttLine({
            new2dVec(0.1, 0),
            new2dVec(0.3, 0),
        }),
        line_l_top = newAbsAttLine({
                new2dVec(0.1, 0),
                new2dVec(0.15, 0),
                new2dVec(0.15, 0.015),
            }),
        line_l_bottom = newAbsAttLine({
                new2dVec(0.1, 0),
                new2dVec(0.15, 0),
                new2dVec(0.15, -0.015),
            }),
        }

    for k, v in pairs(self.attLines) do
        for _, v2 in pairs(v.raw) do
            v2:scale(self.attSize)
        end
    end

    self.boxDisOffset = 0.035 * self.width

    self.attNumberXPos = -(self.attLines.line_l_top.raw[2].x + 0.0625 * 12 * (16 / self.width))
    self:refresh()
end

function absHoloGram:checkProp()
    if hologram_prop[self.name] then
        for k, v in pairs(hologram_prop[self.name]) do
            if k ~= "screen" and self[k] then
                self[k] = v
            end
        end

        for k, v in pairs(self) do
            if k ~= "screen" then
                if not hologram_prop[self.name][k] then
                    if type(v) == "table" then
                        hologram_prop[self.name][k] = table.copy(v)
                    else
                        hologram_prop[self.name][k] = v
                    end
                end
            end
        end
    else
        hologram_prop[self.name] = {
            width = self.width,
            height = self.height,
            rotation = newVec(self.rotation),
            translation = newVec(self.translation),
            scale = self.scale,
            bg = table.copy(self.bg),
            eye_offset = self.eye_offset,
            lint_interval = self.lint_interval,
            drawHoloBorder = self.drawHoloBorder,
            drawInputLine = self.drawInputLine,
            rgb_lock_box = self.rgb_lock_box,
            other_targets = self.other_targets,
            attBorder = self.attBorder,
            attSize = self.attSize,
            msg_bar_offset = self.msg_bar_offset,
            cannon_bar_offset = self.cannon_bar_offset,
            target_bar_offset = self.target_bar_offset,
        }
    end
end

function absHoloGram:refresh()
    self:getSelfPos()

    self.screen.Clear()
    self:radarPage()
    self:attPage()
    self:drawCannon()

    if self.drawHoloBorder then
        self:drawBorder()
    end
    --self.screen.DrawLine(0, self.midPoint.y, self.width, self.midPoint.y, 0x33FFFFFF)
    --self.screen.DrawLine(self.midPoint.x, 0, self.midPoint.x, self.height, 0x33FFFFFF)
    --self:draw_number(new2dVec(1,1), math.floor(flight_control.pitch * 100))
    self.screen.Flush()
end

local holoOffset = newVec(0, 1, 0)
function absHoloGram:getSelfPos()
    local offset = newVec(engine_controller.getShipCenter()):sub(newVec(self.screen.GetBlockPos())):sub(blockOffset):sub(vector.copy(self.translation):add(holoOffset))
    self.worldPos = flight_control.pos:copy():sub(quat.vecRot(flight_control.rot, offset))
    --genParticle(self.worldPos:unpack())
end

function absHoloGram:offset_from_self(v)
    return matrixMultiplication_3d(flight_control.faceMatrix3d, quat.vecRot(quat.nega(flight_control.rot), v:copy():sub(self.worldPos)))
end

function absHoloGram:attPage()
    local eho = newVec(self.eye_offset.x, 0, self.eye_offset.z) --eye_holoCenter_offset
    eho.y = quat.vecRot(flight_control.rot, newVec(0, self.eye_offset.y / 4, 0)).y

    local len2dy = math.sqrt(flight_control.pY.y ^ 2 + flight_control.pZ.y ^ 2)
    local yy = -flight_control.pZ.y / len2dy
    local sinR, cosR = yy, sin2cos(yy)
    if flight_control.pY.y > 0 then
        cosR = -cosR
    end
    local m = {{cosR, -sinR}, {sinR, cosR}}

    local eye_err = math.asin(eho.y / math.sqrt(eho.x ^ 2 + eho.z ^ 2 + eho.y ^ 2))
    local tmpCross_y = eho.x * math.tan(eye_err)
    local tmpCross = new2dVec(0, -tmpCross_y * 2 / self.scale)
    self.crossPos = matrixMultiplication(m, tmpCross):scale(self.midPoint.x):add(self.midPoint)

    local o_ag = math.asin(flight_control.pX.y) - eye_err
    local eye_offset = (eho.x * math.tan(o_ag)) * 2 / self.scale
    if math.abs(eye_offset) < 0.5 - self.attBorder.y then
        self.attLines.line_center:mov(newVec(0, eye_offset, 0))
        self.attLines.line_center:transform(m, true)
        self:drawVec2dgroup(self.midPoint, self.attLines.line_center.list, 0xFFFFFFFF, self.borders.attBorder)
        self:drawVec2dgroup(self.midPoint, self.attLines.line_center.mir, 0xFFFFFFFF, self.borders.attBorder)
    end

    for i = math.rad(self.lint_interval), math.rad(90), math.rad(self.lint_interval) do --想想怎么优化
        local tmp_offset_y = (eho.x * math.tan(i + o_ag)) * 2 / self.scale
        if math.abs(tmp_offset_y) < 0.5 - self.attBorder.y then
            local offset = setmetatable({}, {__index = self.attLines.line_l_top})
            offset:mov(newVec(0, tmp_offset_y, 0))
            offset:transform(m, true)
            self:drawVec2dgroup(self.midPoint, offset.list, 0x33FFFFFF, self.borders.attBorder)
            self:drawVec2dgroup(self.midPoint, offset.mir, 0x33FFFFFF, self.borders.attBorder)

            local numberPos = new2dVec(self.attNumberXPos, 0):add(new2dVec(0, tmp_offset_y))
            numberPos= matrixMultiplication(m, numberPos):scale(self.midPoint.x):add(self.midPoint)
            self:draw_number(numberPos, -math.deg(i))
        end

        local tmp_offset_y_2 = (eho.x * math.tan(o_ag - i)) * 2 / self.scale
        if math.abs(tmp_offset_y_2) < 0.5 - self.attBorder.y and math.abs(i) < 1.55 then
            local offset_nega = setmetatable({}, {__index = self.attLines.line_l_bottom})
            offset_nega:mov(newVec(0, tmp_offset_y_2, 0))
            offset_nega:transform(m, true)
            self:drawVec2dgroup(self.midPoint, offset_nega.list, 0x33FFFFFF, self.borders.attBorder)
            self:drawVec2dgroup(self.midPoint, offset_nega.mir, 0x33FFFFFF, self.borders.attBorder)

            local numberPos = new2dVec(self.attNumberXPos, 0):add(new2dVec(0, tmp_offset_y_2))
            numberPos= matrixMultiplication(m, numberPos):scale(self.midPoint.x):add(self.midPoint)
            self:draw_number(numberPos, math.deg(i))
        end
    end

    self:draw_msg_bar()

end

function absHoloGram:drawCannon()
    local pos = self.cannonCountPos
    for i = 1, #linkedCannons, 1 do
        local yp = pos.y + (i - 1) * 6
        self:draw_5x5_letter(new2dVec(pos.x, yp - 2), linkedCannons[i].name, false, "white")
        self:draw_number(new2dVec(pos.x + 50, yp), linkedCannons[i].bullets_count, true)
        if linkedCannons[i].cross_point then
            self:drawCannonCross(linkedCannons[i].cross_point, cross_box)
        end
    end
end

function absHoloGram:radarPage()
    self:draw_5x5_letter(self.radarPos_left, "<")
    self:draw_5x5_letter(self.radarPos_right, ">")
    if properties.radarMode == 1 then --nil
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_65e0")
        else
            self:draw_5x5_letter(self.radarModePos, "null", "white")
        end
    elseif properties.radarMode == 2 then --vs_ship
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_74e6,_5c14,_57fa,_91cc")
        else
            self:draw_5x5_letter(self.radarModePos, "vs_ship", "white")
        end
    elseif properties.radarMode == 3 then --monster
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_602a,_7269")
        else
            self:draw_5x5_letter(self.radarModePos, "怪物", "white")
        end
    elseif properties.radarMode == 4 then --player
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_73a9,_5bb6")
        else
            self:draw_5x5_letter(self.radarModePos, "player", "white")
        end
    elseif properties.radarMode == 5 then --mobs
        if properties.language == language[1] then
            self:draw_7x7_fonts(self.radarModePos, "_751f,_7269")
        else
            self:draw_5x5_letter(self.radarModePos, "mobs", "white")
        end
    end
    
    for k, v in pairs(self.locked_list) do
        v.flag = false
    end

    if radar.final_targets then
        for k, v in pairs(radar.final_targets) do
            if self.locked_list[v.id] then
                self.locked_list[v.id].flag = true
                self.locked_list[v.id].pos = newVec(v.x, v.y, v.z)
            else
                self.locked_list[v.id] = { name = v.name, anime_count = 12, flag = true, pos = newVec(v.x, v.y, v.z), box = new_lock_box()}
                if v.health then
                    self.locked_list[v.id].health = v.health
                    self.locked_list[v.id].maxHealth = v.maxHealth
                end
            end
        end
    end

    for k, v in pairs(self.locked_list) do
        if not v.flag then
            self.locked_list[k] = nil
        end
    end

    local fflag = true
    for k, v in pairs(self.locked_list) do
        self:drawLockBox(v.pos, v.box, v.anime_count)
        self.locked_list[k].anime_count = v.anime_count > 0 and self.locked_list[k].anime_count - 1 or 0

        if fflag then
            fflag  = false
            self:draw_5x5_letter(self.targetPos, filterString(self.locked_list[k].name), "white")
            if self.locked_list[k].health then
                local x_offset = (self.locked_list[k].health / self.locked_list[k].maxHealth) * self.hp_bar_len * self.midPoint.x
                self.screen.DrawLine(self.hp_pos_start.x, self.hp_pos_start.y, self.hp_pos_start.x + x_offset, self.hp_pos_end.y, 0x11FF11FF, 1)
                self.screen.DrawLine(self.hp_pos_start.x + x_offset, self.hp_pos_start.y, self.hp_pos_end.x, self.hp_pos_end.y, 0x1111FFFF, 1)
                self:draw_number(self.hp_text_start, self.locked_list[k].health)
                self:draw_number(self.hp_text_end, self.locked_list[k].maxHealth)
            end
        end
    end

    -----其它目标-----
    if self.other_targets then
        for k, v in pairs(self.other_list) do
            v.flag = false
        end
    
        if radar.other_targets then
            for k, v in pairs(radar.other_targets) do
                if self.other_list[v.id] then
                    self.other_list[v.id].flag = true
                    self.other_list[v.id].pos = newVec(v.x, v.y, v.z)
                else
                    self.other_list[v.id] = { name = v.name, flag = true, pos = newVec(v.x, v.y, v.z), box = new_other_lock_box()}
                end
            end
        end
    
        for k, v in pairs(self.other_list) do
            if not v.flag then
                self.other_list[k] = nil
            else
                self:drawOtherBox(v.pos, v.box)
            end
        end
    end
end

local rot_box_matrix = {
    {math.cos(math.rad(18)), math.sin(math.rad(18))},
    {-math.sin(math.rad(18)), math.cos(math.rad(18))}
}
local scale_lower = {
    { 0.82, 0 }, { 0, 0.82 }
}

function absHoloGram:getLockPos(v)
    local e2e = self.eye_offset:copy()
    --e2e.y = e2e.y * self.scale
    local pos = self:offset_from_self(v):sub(e2e)
    local len = pos:len()
    local sin_y = pos.y / len
    local sin_x = (-pos.z / len)
    local xx = self.eye_offset.x * math.tan(math.asin(sin_x / sin2cos(sin_y)))
    local yy = self.eye_offset.x * math.tan(math.asin(sin_y / sin2cos(sin_x)))
    local point = new2dVec(xx / self.scale, yy / self.scale):scale(self.width):add(self.midPoint)
    point.y = point.y + self.eye_pitch_offset
    return point, len
end

function absHoloGram:drawLockBox(v, box, anime_index)
    local point, len = self:getLockPos(v)
    box:transform(rot_box_matrix, false)
    if anime_index > 0 then
        box:transform(scale_lower, false)
        if self.rgb_lock_box then
            local g_b = anime_index * 20
            local color = tonumber(string.format("%x%x%x%x", (12 - anime_index) * 20, g_b, 51, 255), 16)
            self:drawVec2dgroup(point, box.list, color, self.borders.full)
        else
            self:drawVec2dgroup(point, box.list, 0xFF3333FF, self.borders.full)
        end
    else
        self:drawVec2dgroup(point, box.list, 0xFF3333FF, self.borders.full)
        self:draw_number(new2dVec(point.x + self.boxDisOffset, point.y), math.floor(len))
    end
end

function absHoloGram:drawOtherBox(v, box)
    local point, len = self:getLockPos(v)
    self:drawVec2dgroup(point, box.list, 0x3333FFFF, self.borders.full)
end

function absHoloGram:drawCannonCross(v, box)
    local point = self:getLockPos(v)
    self:drawVec2dgroup(point, box.list, 0x33FF33FF, self.borders.full)
end

function absHoloGram:drawVec2dgroup(point, list, color, border)
    for i, v in ipairs(list) do
        local next = list[i + 1]
        if next then
            local p1 = new2dVec(v.x, v.y):scale(self.midPoint.x):add(point)
            local p2 = new2dVec(next.x, next.y):scale(self.midPoint.x):add(point)
            if self:checkVecArea(p1, border) and self:checkVecArea(p2, border) then
                self.screen.DrawLine(p1.x, p1.y, p2.x, p2.y, color, 1)
            end
        end
    end
end

function absHoloGram:checkVecArea(v2d, border)
    return v2d.x > border.x and v2d.x < self.width - border.x
        and v2d.y > border.y and v2d.y < self.height - border.y
end

function absHoloGram:drawBorder()
    self.screen.DrawLine(0, 0, 30, 0, 0xFFFFFFFF, 1)
    self.screen.DrawLine(0, 0, 0, 30, 0xFFFFFFFF, 1)

    self.screen.DrawLine(self.width - 30, 0, self.width, 0, 0xFFFFFFFF, 1)
    self.screen.DrawLine(self.width - 1, 0, self.width - 1, 30, 0xFFFFFFFF, 1)

    self.screen.DrawLine(self.width - 1, self.height - 30, self.width - 1, self.height, 0xFFFFFFFF, 1)
    self.screen.DrawLine(self.width - 1, self.height - 1, self.width - 30, self.height - 1, 0xFFFFFFFF, 1)
    self.screen.DrawLine(0, self.height - 30, 0, self.height - 1, 0xFFFFFFFF, 1)
    self.screen.DrawLine(0, self.height - 1, 30, self.height - 1, 0xFFFFFFFF, 1)
end

local coupled_ct = 20
function absHoloGram:draw_msg_bar()
    local ct = controllers.activated
    if self.drawInputLine and ct then
        local joy = new2dVec(-ct.LeftStick.x,ct.RightStick.y)
        local joy2len = joy:len()
        joy2len = joy2len > 1 and 1 or joy2len
        joy:norm():scale(joy2len)
        local right_joy_pos = new2dVec(self.crossPos):add(joy:scale(self.midPoint.x):scale(0.3))
        self.screen.DrawLine(self.crossPos.x, self.crossPos.y, right_joy_pos.x, right_joy_pos.y, 0xFFFFFF33, 1)
    end

    self:draw_number(self.heightPos, flight_control.pos.y)
    if properties.language == language[1] then
        self:draw_7x7_fonts(self.hightFontPos, "_9ad8,_5ea6,_space,_7c73")
    else
        self:draw_5x5_letter(self.hightFontPos, "height", "white")
    end
    self:draw_number(self.speedPos, flight_control.speed * 3.6)
    self:draw_5x5_letter(self.speedFontPos, "km/h")

    self:draw_5x5_letter(self.energyFontPos, "need rpm")
    self:draw_number(self.energyPos, flight_control.mass / 20000)
    if properties.mode == 1 then
        self:draw_5x5_letter(self.ThrottlePos, "throttle")
        self:draw_number(self.ThrottleFontPos, properties.spaceShipThrottle, true)
        if flight_control.hold then
            self:draw_5x5_letter(self.holdingPos, "holding")
        end
    end
    if properties.coupled then
        self:draw_5x5_letter(self.massPos, "coupled", "white") --Decoupled
    elseif coupled_ct > 10 then
        self:draw_5x5_letter(self.massPos, "decoupled", "red")
    end
    coupled_ct = coupled_ct > 1 and coupled_ct - 1 or 20
    --self:draw_number(self.massPos, math.abs(flight_control.all_force / 1000))
    --self:draw_number(self.massPos, math.deg(math.asin(flight_control.pX.y) * 100))

    --self.screen.Blit(self.crossPos.x, self.crossPos.y, 1, 1, {0xFFFFFFFF}, 1)
    if properties.radar_lock_mode then
        self:draw_5x5_letter(new2dVec(self.crossPos.x + 2, self.crossPos.y - 2), "_cross")
    else
        self:draw_5x5_letter(new2dVec(self.crossPos.x + 2, self.crossPos.y - 2), "_cross_box")
    end
end

function absHoloGram:draw_number(pos, n, white)
    if type(n) ~= "number" then
        return
    end
    n = math.floor(n + 0.5)
    local str = tostring(math.abs(n))
    local x = pos.x - 2 * #str
    local y = pos.y - 2
    if white then
        if n < 0 then
            self.screen.DrawLine(x, y + 2, x + 4, y + 2, 0xFFFFFFFF, 1)
            x = x + 5
        end
    
        for i = 1, #str, 1 do
            self.screen.Blit(x, y, 4, 5, my_4x5_number_white[tonumber(str:sub(i, i)) + 1], 1)
            x = x + 5
        end
    else
        if n < 0 then
            self.screen.DrawLine(x, y + 2, x + 4, y + 2, 0x33FFFFFF, 1)
            x = x + 5
        end
    
        for i = 1, #str, 1 do
            self.screen.Blit(x, y, 4, 5, my_4x5_number[tonumber(str:sub(i, i)) + 1], 1)
            x = x + 5
        end
    end
end

function absHoloGram:draw_7x7_fonts(pos, uni_arr)
    local arrs = split(uni_arr, ",")
    local x, y = pos.x - #arrs / 2 * 8, pos.y
    for i, v in ipairs(arrs) do
        if v ~= "_space" then
            self.screen.Blit(x, y, 7, 7, jizhi_7x7_fonts[v], 1)
        end
        x = x + 8
    end
end

function absHoloGram:draw_5x5_letter(pos, str, color, left)
    local arrs
    if str == "_cross" or str == "_cross_box" then
        arrs = {str}
    else
        str = string.lower(str)
        arrs = stringToCharArray(str)
    end
    local x, y = 0, pos.y
    if left then
        x = pos.x
    else
        x = pos.x - #arrs / 2 * 6
    end
    for i, v in ipairs(arrs) do
        if v ~= " " then
            if color == "white" then
                self.screen.Blit(x, y, 5, 5, my_5x5_letter_white[v], 1)
            elseif color == "red" then
                self.screen.Blit(x, y, 5, 5, my_5x5_letter_red[v], 1)
            else
                self.screen.Blit(x, y, 5, 5, my_5x5_letter[v], 1)
            end
        end
        x = x + 6
    end
end

hologram_manager = {
    holograms = {}
}

function hologram_manager:getAllHoloGram()
    local holograms = { peripheral.find("hologram") }
    for k, v in pairs(holograms) do
        local tmp = setmetatable({ name = v.GetName(), screen = v }, {__index = absHoloGram})
        self.holograms[k] = tmp
    end
    hologram_manager:initAll()
end

function hologram_manager:initAll()
    for k, v in pairs(hologram_prop) do
        v.flag = false
    end

    for k, v in pairs(self.holograms) do
        v:init()
        if hologram_prop[v.name] then
            hologram_prop[v.name].flag = true
        end
    end

    for k, v in pairs(hologram_prop) do
        if not v.flag then
            hologram_prop[k] = nil
        end
    end

end

function hologram_manager:refresh()
    for k, v in pairs(self.holograms) do
        v:refresh()
    end
end

-- abstractScreen
-- 空屏幕，所有其他屏幕类的基类
local abstractScreen = {
    screenTitle = "blank"
}
abstractScreen.__index = abstractScreen

function abstractScreen:init() end

function abstractScreen:refresh() end

function abstractScreen:onTouch(x, y) end

function abstractScreen:onDisconnect()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write("[DISCONNECTED]")
end

function abstractScreen:onRootFatal()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
end

function abstractScreen:onSystemSleep()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    if self.monitor.setTextScale then
        self.monitor.setTextScale(0.5)
        local x, y = self.monitor.getSize()
        self.monitor.setCursorPos(x / 2 - 6, y / 2)
        self.monitor.write("click to restart")
    end
end

function abstractScreen:onBlank()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    if self.monitor.setTextScale then
        self.monitor.setTextScale(1)
    end
end

function abstractScreen:report() end

------------Window------------
local abstractWindow, abstractMonitor, flightPages = {}, {}, {}

function abstractWindow:init() end

function abstractWindow:new(parent, nX, nY, nWidth, nHeight, visible)
    self.window = window.create(parent, nX, nY, nWidth, nHeight, visible)
end

function abstractWindow:refreshButtons(cut, page, rowCut) --没有参数时打印所有按钮，有参数时：cut前面的正常打印，cut后面的开始翻页
    if not self.buttons then
        for i = 1, #self.buttons, 1 do
            self.buttons[i].x = math.floor(self.buttons[i].x)
            self.buttons[i].y = math.floor(self.buttons[i].y)
        end
    end
    if not cut then
        cut = #self.buttons
    end
    if self.window.isVisible() then
        self:clear()
        for i = 1, cut, 1 do
            local bt = self.buttons[i]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end

    if page then
        local start = (page - 1) * (self.height - rowCut) + 1 + cut
        for i = start, page * (self.height - rowCut) + cut, 1 do
            if i > #self.buttons then break end
            local bt = self.buttons[i]
            self.window.setCursorPos(bt.x, bt.y - (page - 1) * (self.height - rowCut))
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end
end

function abstractWindow:clear()
    self.window.setBackgroundColor(getColorDec(properties.bg))
    self.window.clear()
    self.window.setCursorPos(1, 1)
end

function abstractWindow:switchWindow(index)
    local result = properties.winIndex[self.name][self.row][self.column] + index
    if result > 4 then
        result = 1
    elseif result == 0 then
        result = 4
    end
    properties.winIndex[self.name][self.row][self.column] = result
    return result
end

function abstractWindow:nextPage(x, y)
    if y == 1 then
        if x < self.width / 2 then
            return self:switchWindow(-1)
        elseif x > self.width / 2 then
            return self:switchWindow(1)
        end
    end
end

function abstractWindow:subPage_Back(x, y)
    if x <= 2 and y == 1 then
        system:updatePersistentData()
        properties.winIndex[self.name][self.row][self.column] = self.indexFlag
    end
end

function abstractWindow:refreshTitle()
    self.window.setCursorPos(3, 1)
    self.window.blit(self.pageName, genStr(properties.title, #self.pageName), genStr(properties.bg, #self.pageName))
end

local page_attach_manager  = {}
local modPage              = setmetatable({ pageId = 1, pageName = "modPage" }, { __index = abstractWindow })
local shipNetPage          = setmetatable({ pageId = 2, pageName = "shipNetPage" }, { __index = abstractWindow })
local attPage              = setmetatable({ pageId = 3, pageName = "attPage" }, { __index = abstractWindow })
local setPage              = setmetatable({ pageId = 4, pageName = "setPage" }, { __index = abstractWindow })
local set_spaceShip        = setmetatable({ pageId = 5, pageName = "set_spaceShip" }, { __index = abstractWindow })
local set_quadFPV          = setmetatable({ pageId = 6, pageName = "set_quadFPV" }, { __index = abstractWindow })
local set_helicopter       = setmetatable({ pageId = 7, pageName = "set_helicopter" }, { __index = abstractWindow })
local set_airShip          = setmetatable({ pageId = 8, pageName = "set_airShip" }, { __index = abstractWindow })
local set_user             = setmetatable({ pageId = 9,  pageName = "user_Change" }, { __index = abstractWindow })
local set_home             = setmetatable({ pageId = 10, pageName = "home_set" }, { __index = abstractWindow })
local set_simulate         = setmetatable({ pageId = 11, pageName = "simulate" }, { __index = abstractWindow })
local set_other            = setmetatable({ pageId = 12, pageName = "set_other" }, { __index = abstractWindow })
local set_profile          = setmetatable({ pageId = 13, pageName = "profile" }, { __index = abstractWindow })
local set_colortheme       = setmetatable({ pageId = 14, pageName = "colortheme" }, { __index = abstractWindow })
local shipNet_set_Page     = setmetatable({ pageId = 15, pageName = "shipNet_set" }, { __index = abstractWindow })
local shipNet_connect_Page = setmetatable({ pageId = 16, pageName = "shipNet_call" }, { __index = abstractWindow })
local set_camera           = setmetatable({ pageId = 17, pageName = "set_camera" }, { __index = abstractWindow })
local set_shipFollow       = setmetatable({ pageId = 18, pageName = "set_shipFollow" }, { __index = abstractWindow })
local set_anchorage        = setmetatable({ pageId = 19, pageName = "set_anchorage" }, { __index = abstractWindow })
local mass_fix             = setmetatable({ pageId = 20, pageName = "mass_fix" }, { __index = abstractWindow })
local rate_Roll            = setmetatable({ pageId = 21, pageName = "rate_Roll" }, { __index = abstractWindow })
local rate_Yaw             = setmetatable({ pageId = 22, pageName = "rate_Yaw" }, { __index = abstractWindow })
local rate_Pitch           = setmetatable({ pageId = 23, pageName = "rate_Pitch" }, { __index = abstractWindow })
local set_fixedWing        = setmetatable({ pageId = 24, pageName = "set_fixedWing" }, { __index = abstractWindow })
local set_followRange      = setmetatable({ pageId = 25, pageName = "set_followRange" }, { __index = abstractWindow })
local recordings           = setmetatable({ pageId = 26, pageName = "recordings" }, { __index = abstractWindow })

flightPages                = {
    modPage,              --1
    shipNetPage,          --2
    attPage,              --3
    setPage,              --4
    set_spaceShip,        --5
    set_quadFPV,          --6
    set_helicopter,       --7
    set_airShip,          --8
    set_user,             --9
    set_home,             --10
    set_simulate,         --11
    set_other,            --12
    set_profile,          --13
    set_colortheme,       --14
    shipNet_set_Page,     --15
    shipNet_connect_Page, --16
    set_camera,           --17
    set_shipFollow,       --18
    set_anchorage,        --19
    mass_fix,             --20
    rate_Roll,            --21
    rate_Yaw,             --22
    rate_Pitch,           --23
    set_fixedWing,        --24
    set_followRange,      --25
    recordings            --26
}

--winIndex = 1
function modPage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.buttons = {
        { text = "<    MOD    >",   x = self.width / 2 - 5, y = 1,               blitF = genStr(title, 13),                blitB = genStr(bg, 13) },
        { text = "[|]",             x = 3,                  y = self.height - 1, blitF = "eee",                            blitB = genStr(bg, 3) },
        { text = "[R]",             x = 6,                  y = self.height - 1, blitF = "222",                            blitB = genStr(bg, 3) },
        { text = "[x]",             x = self.width - 5,     y = self.height - 1, blitF = "888",                            blitB = genStr(bg, 3) },
        { text = modelist[1].name,  x = 2,                  y = 3,               blitF = genStr(font, #modelist[1].name),  blitB = genStr(bg, #modelist[1].name),  modeId = 1,  select = genStr(select, #modelist[1].name) },
        { text = modelist[2].name,  x = 2,                  y = 4,               blitF = genStr(font, #modelist[2].name),  blitB = genStr(bg, #modelist[2].name),  modeId = 2,  select = genStr(select, #modelist[2].name) },
        { text = modelist[3].name,  x = 2,                  y = 5,               blitF = genStr(font, #modelist[3].name),  blitB = genStr(bg, #modelist[3].name),  modeId = 3,  select = genStr(select, #modelist[3].name) },
        { text = modelist[4].name,  x = 2,                  y = 6,               blitF = genStr(font, #modelist[4].name),  blitB = genStr(bg, #modelist[4].name),  modeId = 4,  select = genStr(select, #modelist[4].name) },
        { text = modelist[5].name,  x = 2,                  y = 7,               blitF = genStr(font, #modelist[5].name),  blitB = genStr(bg, #modelist[5].name),  modeId = 5,  select = genStr(select, #modelist[5].name) },
        { text = modelist[6].name,  x = 2,                  y = 8,               blitF = genStr(font, #modelist[6].name),  blitB = genStr(bg, #modelist[6].name),  modeId = 6,  select = genStr(select, #modelist[6].name) },
        { text = modelist[7].name,  x = 2,                  y = 9,               blitF = genStr(font, #modelist[7].name),  blitB = genStr(bg, #modelist[7].name),  modeId = 7,  select = genStr(select, #modelist[7].name) },
        { text = modelist[8].name,  x = 2,                  y = 10,              blitF = genStr(font, #modelist[8].name),  blitB = genStr(bg, #modelist[8].name),  modeId = 8,  select = genStr(select, #modelist[8].name) },
        { text = modelist[9].name,  x = 2,                  y = 11,              blitF = genStr(font, #modelist[9].name),  blitB = genStr(bg, #modelist[9].name),  modeId = 9,  select = genStr(select, #modelist[9].name) },
        { text = modelist[10].name, x = 2,                  y = 12,              blitF = genStr(font, #modelist[10].name), blitB = genStr(bg, #modelist[10].name), modeId = 10, select = genStr(select, #modelist[10].name) },
        { text = modelist[11].name, x = 2,                  y = 13,              blitF = genStr(font, #modelist[11].name), blitB = genStr(bg, #modelist[11].name), modeId = 11, select = genStr(select, #modelist[11].name) },
        { text = modelist[12].name, x = 2,                  y = 14,              blitF = genStr(font, #modelist[12].name), blitB = genStr(bg, #modelist[12].name), modeId = 12, select = genStr(select, #modelist[12].name) },
        { text = modelist[13].name, x = 2,                  y = 15,              blitF = genStr(font, #modelist[13].name), blitB = genStr(bg, #modelist[13].name), modeId = 13, select = genStr(select, #modelist[13].name) },
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 2, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.pageIndex = 1
    self.cutRow = 5 --不需要分页的区域总行高
end

function modPage:refresh()
    self:refreshButtons(4, self.pageIndex, self.cutRow)
    for k, v in pairs(self.buttons) do
        if v.text == modelist[properties.mode].name then
            local yPos = v.y - (self.pageIndex - 1) * (self.height - self.cutRow)
            if yPos > 2 and yPos < self.height - 1 then
                self.window.setCursorPos(v.x, v.y - (self.pageIndex - 1) * (self.height - self.cutRow))
                self.window.blit(v.text, v.blitB, v.select)
                if properties.lock and v.modeId < 5 then
                    self.window.blit(" L", " " .. properties.other,
                        genStr(properties.bg, 2))
                end
            end
        end
    end
    if #self.buttons > self.height - self.cutRow then
        if self.pageIndex == 1 or self.pageIndex * (self.height - self.cutRow) < #self.buttons - 4 then
            local bt = self.otherButtons[1]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
        if self.pageIndex > 1 then
            local bt = self.otherButtons[2]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end
    if self.row == 1 and self.pageIndex == 1 then
        self.window.setCursorPos(self.width / 2 - 5, 2)
        if engineOff then
            self.window.blit("engine_OFF", genStr(properties.other, 10), genStr(properties.bg, 10))
        else
            self.window.blit("engine_ON ", genStr(properties.other, 10), genStr(properties.bg, 10))
        end
    end
end

function modPage:onTouch(x, y)
    self:nextPage(x, y)
    if y == 2 then
        if self.row == 1 and self.pageIndex == 1 then
            if y == 2 and x >= self.width / 2 - 5 and x <= self.width / 2 + 5 then
                engineOff = not engineOff
            end
        elseif self.pageIndex > 1 then
            self.pageIndex = self.pageIndex - 1
        end
    elseif y == self.height - 1 then
        if x >= self.buttons[2].x and x < self.buttons[3].x + 3 then
            system:updatePersistentData()
            if x > 5 then
                os.reboot()
            else
                shutdown_flag = true
                monitorUtil.onSystemSleep()
            end
        elseif x > self.buttons[4].x then
            monitorUtil.disconnect(self.name)
        end
    elseif y < self.height - 2 and y > 2 then
        for k, v in pairs(self.buttons) do
            if v.y > 1 then
                if x >= v.x and x < v.x + #v.text + 2 and y == v.y - (self.pageIndex - 1) * (self.height - self.cutRow) then
                    if v.modeId then
                        if properties.mode == v.modeId then
                            if v.modeId < 5 then
                                properties.lock = not properties.lock
                            end
                        else
                            if v.modeId < 8 or v.modeId >= 12 then
                                properties.mode = v.modeId
                            elseif parentShip.id ~= -1 then
                                properties.mode = v.modeId
                            end
                        end
                    end
                end
            end
        end
        flight_control:setLastPos()
    elseif y == self.otherButtons[1].y then
        if #self.buttons - 1 > self.pageIndex * (self.height - self.cutRow) then
            self.pageIndex = self.pageIndex + 1
        else
            self.pageIndex = 1
        end
    end
end

--winIndex = 2
function attPage:init()
end

function attPage:refresh()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos
    if info ~= -1 then
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        if info.row == 1 then yPos = yPos + 1 end
    else
        width, height, xPos, yPos = self.width, self.height, 1, 1
    end
    self.window.setBackgroundColor(getColorDec(properties.bg))
    self.window.clear()
    self.window.setCursorPos(1, 1)
    if yPos == 1 then
        for i = 1, self.width, 1 do
            self.window.setCursorPos(i, 1)
            self.window.blit(".", font, bg)
        end

        local xMid = width / 2
        local xPoint = math.floor(-math.sin(math.rad(flight_control.yaw)) * xMid + 0.5)
        local zPoint = math.floor(-math.cos(math.rad(flight_control.yaw)) * xMid + 0.5)
        if flight_control.pX.x > 0 then
            self.window.setCursorPos(xMid + zPoint - xPos, 1)
            self.window.blit("E", select, bg)
        else
            self.window.setCursorPos(xMid - zPoint - xPos, 1)
            self.window.blit("W", select, bg)
        end

        if flight_control.pX.z > 0 then
            self.window.setCursorPos(xMid + xPoint - xPos, 1)
            self.window.blit("S", select, bg)
        else
            self.window.setCursorPos(xMid - xPoint - xPos, 1)
            self.window.blit("N", select, bg)
        end
    end

    local yMid = height / 2
    local lPointy = math.abs(flight_control.pitch) > 90 and flight_control.pZ.y or -flight_control.pZ.y
    lPointy = math.floor(lPointy * yMid + 0.5)
    lPointy = math.abs(lPointy) > yMid - 1 and copysign(yMid - 1, lPointy) or lPointy
    local xPointy = math.abs(flight_control.pitch) > 90 and -flight_control.pX.y or flight_control.pX.y
    xPointy = yMid + math.floor(xPointy * height + 0.5)
    local lline, rline = width * 0.33 - xPos + 2, width * 0.66 - xPos + 2
    for i = 1, height, 1 do
        local yy = i - yPos + 2
        if yPos > 1 then
            yy = yy - 1
        end
        if yy > 0 then
            self.window.setCursorPos(lline - 2, yy)
            if i == yMid + lPointy then
                self.window.blit("-", font, bg)
            end
            self.window.setCursorPos(lline, yy)
            if i == xPointy then
                self.window.blit(">-", font .. select, genStr(bg, 2))
            else
                self.window.setCursorPos(lline + 1, yy)
                self.window.blit("-", font, bg)
            end

            self.window.setCursorPos(rline + 3, yy)
            if i == yMid - lPointy then
                self.window.blit("-", font, bg)
            end
            self.window.setCursorPos(rline, yy)
            if i == xPointy then
                self.window.blit("-<", select .. font, genStr(bg, 2))
            else
                self.window.blit("-", font, bg)
            end
        end
    end
    if xPointy < 2 and yPos == 1 then
        self.window.setCursorPos(lline, 2)
        self.window.blit("^", font, bg)
        self.window.setCursorPos(rline + 1, 2)
        self.window.blit("^", font, bg)
    elseif xPointy > height - 1 then
        if info ~= -1 then
            if info.row ~= info.maxRow then goto continue end
        end
        self.window.setCursorPos(lline, self.height)
        self.window.blit("v", font, bg)
        self.window.setCursorPos(rline + 1, self.height)
        self.window.blit("v", font, bg)
    end
    ::continue::

    local joyUtil = controllers.activated
    local mod = modelist[properties.mode].name
    if info ~= -1 then
        if info.maxColumn > 1 then
            local x, y = width / 2 - xPos, height / 2 - yPos
            if yPos > 1 then y = y - 1 end

            self.window.setCursorPos(x - #mod / 2 + 2, yPos + 1)
            self.window.blit(mod, genStr(title, #mod), genStr(bg, #mod))
            if mod == "SpaceShip" then
                if joyUtil and joyUtil.LeftJoyClick then
                    self.window.setCursorPos(x - 3, y)
                    self.window.blit("!燃烧中!", "fffffffff", "eeeeeeeee")
                else
                    self.window.setCursorPos(x - 2, y - 1)
                    if properties.coupled then
                        self.window.blit("耦合", genStr(bg, 7), genStr(select, 7))
                    else
                        self.window.blit("耦合", genStr(font, 7), genStr(bg, 7))
                    end
                end
            else
                self.window.setCursorPos(x - 3, y - 1)
                self.window.blit(("ROLL %6.1f"):format(flight_control.roll), genStr(other, 11), genStr(bg, 11))
                self.window.setCursorPos(x - 3, y)
                self.window.blit(("YAW  %6.1f"):format(flight_control.yaw), genStr(other, 11), genStr(bg, 11))
                self.window.setCursorPos(x - 3, y + 1)
                self.window.blit(("PITCH%6.1f"):format(flight_control.pitch), genStr(other, 11), genStr(bg, 11))
            end
            self.window.setCursorPos(x - 2, y + 2)
            self.window.blit("调节 >", genStr(bg, 8), genStr(select, 8))
            self.window.setCursorPos(x - 2, y + 3)
            if properties.lock then
                self.window.blit("锁定 开", genStr(bg, 8), genStr(other, 8))
            else
                self.window.blit("锁定 关", genStr(other, 8), genStr(bg, 8))
            end

            self.window.setCursorPos(x - 3, y + 4)
            self.window.blit(("%6.1f km/h"):format(flight_control.speed * 3.6), genStr(select, 11), genStr(bg, 11))
            self.window.setCursorPos(x - 3, y + 5)
            if flight_control.pos.y < 99999 then
                self.window.blit(("H %7.1f m"):format(flight_control.pos.y), genStr(select, 11), genStr(bg, 11))
            else
                self.window.blit(("H  %5.1f km"):format(flight_control.pos.y / 1000), genStr(select, 11), genStr(bg, 11))
            end

            if joyUtil and info.maxColumn > 2 then
                self.window.setCursorPos(x - self.width - joyUtil.LeftStick.x * (self.width / 2 - 2),
                    y + 2 - joyUtil.LeftStick.y * (height / 2 - 1))
                self.window.blit("*", font, select)
                self.window.setCursorPos(x + self.width + 3 - joyUtil.RightStick.x * (self.width / 2 - 2),
                    y + 2 - joyUtil.RightStick.y * (height / 2 - 1))
                self.window.blit("*", font, select)

                for i = 1, joyUtil.LT * height - 2, 1 do
                    self.window.setCursorPos(x - 5, height - yPos - i + 1)
                    self.window.blit("^", bg, font)
                end
                for i = 1, joyUtil.RT * height - 2, 1 do
                    self.window.setCursorPos(x + 9, height - yPos - i + 1)
                    self.window.blit("^", bg, font)
                end
            end
        else
            if mod == "SpaceShip" or mod == "QuadFPV" then
                self:drawSpeed(mod, bg, font, title, select, other)
            end
        end
    else
        if mod == "SpaceShip" or mod == "QuadFPV" then
            self:drawSpeed(mod, bg, font, title, select, other)
        end
    end
end

function attPage:drawSpeed(mod, bg, font, title, select, other)
    if mod == "SpaceShip" then
        self.window.setCursorPos(math.floor(self.width / 2) + 1, math.floor(self.height / 2))
        if properties.coupled then
            self.window.blit("C", bg, select)
        else
            self.window.blit("C", font, bg)
        end
    end
    self.window.setCursorPos(math.floor(self.width / 2), math.floor(self.height / 2) + 2)
    local flaaag = flight_control.speed < 999
    local speeeed = flaaag and flight_control.speed or flight_control.speed / 1000

    local str = string.format("%3d", speeeed)
    self.window.blit(str, genStr(font, 3), genStr(bg, 3))
    self.window.setCursorPos(math.floor(self.width / 2), math.floor(self.height / 2) + 3)
    self.window.blit(flaaag and "m/s" or " km", genStr(other, 3), genStr(bg, 3))
end

function attPage:onTouch(x, y)
    self:nextPage(x, y)
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos
    if info ~= -1 then
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        if info.row == 1 then yPos = yPos + 1 end
    else
        width, height, xPos, yPos = self.width, self.height, 1, 1
    end
    local mod = modelist[properties.mode].name
    if info ~= -1 then
        if info.maxColumn > 1 then
            local bx, by = width / 2 - xPos, height / 2 - yPos
            if yPos > 1 then y = y - 1 end
            if y == by + 2 and x >= bx - 2 and x <= bx + 10 then
                local index
                if mod == "SpaceShip" then
                    index = 5
                elseif mod == "QuadFPV" then
                    index = 6
                elseif mod == "Helicopter" then
                    index = 7
                elseif mod == "AirShip" then
                    index = 8
                end
                if index then
                    self.windows[self.row][self.column][index].indexFlag = 3
                    properties.winIndex[self.name][self.row][self.column] = index
                end
            elseif y == by + 3 and x >= bx - 2 and x <= bx + 10 then
                flight_control:setLastPos()
                properties.lock = not properties.lock
            elseif y == by - 1 and x >= bx - 2 and x <= bx + 9 then
                properties.coupled = not properties.coupled
            end
        else
            if mod == "SpaceShip" then
                if y == math.floor(self.height / 2) and x == math.floor(self.width / 2) + 1 then
                    properties.coupled = not properties.coupled
                end
            end
        end
    else
        if mod == "SpaceShip" then
            if y == math.floor(self.height / 2) and x == math.floor(self.width / 2) + 1 then
                properties.coupled = not properties.coupled
            end
        end
    end
end

--winIndex = 3
function shipNetPage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.buttons = {
        { text = "<  飞船网络  >", x = self.width / 2 - 5, y = 1,               blitF = genStr(title, 13), blitB = genStr(bg, 13) },
        { text = "设置",           x = 2,                  y = self.height - 1, blitF = genStr(bg, 3),     blitB = genStr(select, 3) },
        { text = "连接",       x = self.width - 7,     y = self.height - 1, blitF = genStr(bg, 7),     blitB = genStr(select, 7) },
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 2, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.callBlink = 10
    self.callInBlink = 10
    self.pageIndex = 1
end

function shipNetPage:refresh()
    self.window.setBackgroundColor(getColorDec(properties.bg))
    self.window.clear()
    self.window.setCursorPos(1, 1)
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos
    if info ~= -1 then --页面拼接
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        if info.row == 1 then yPos = yPos + 1 end
    else
        width, height, xPos, yPos = self.width, self.height, 1, 1
    end

    local sp, ep = 1, #self.buttons
    if info ~= -1 then --页面拼接时把下面两个分项按钮移到底部
        if info.maxRow > 1 and yPos > 1 then
            sp = 2
        elseif info.maxRow > 1 and yPos == 1 then
            sp, ep = 1, 1
        end
    end

    if #callList > 0 then --收到连接请求
        if self.callInBlink % 2 == 0 then
            self.buttons[3].blitF = genStr(select, 7)
            self.buttons[3].blitB = genStr(bg, 7)
        else
            self.buttons[3].blitF = genStr(bg, 7)
            self.buttons[3].blitB = genStr(select, 7)
        end
        self.callInBlink = self.callInBlink - 0.5 > 0 and self.callInBlink - 0.5 or 10
    else
        self.buttons[3].blitF = genStr(bg, 7)
        self.buttons[3].blitB = genStr(select, 7)
    end

    for i = sp, ep, 1 do
        local x, y = self.buttons[i].x, self.buttons[i].y
        if i > 1 then y = height - yPos end
        if yPos > 1 then y = y - 1 end
        self.window.setCursorPos(x, y)
        self.window.blit(self.buttons[i].text, self.buttons[i].blitF, self.buttons[i].blitB)
    end


    if #shipNet_list > height - 5 then --如果超过一页, 显示翻页键
        local x, y = (self.width - #self.otherButtons[1].text) / 2 + 1, height - yPos - 1
        if yPos > 1 then y = y - 1 end
        self.window.setCursorPos(x, y)
        self.window.blit(self.otherButtons[1].text, self.otherButtons[1].blitF, self.otherButtons[1].blitB)
        if self.pageIndex > 1 then
            self.window.setCursorPos(x, 2)
            self.window.blit(self.otherButtons[2].text, self.otherButtons[2].blitF, self.otherButtons[2].blitB)
        end
    end

    ---------连接中的船区分颜色---------
    local listLen = height - 5
    local index = #shipNet_list > height - 5 and listLen * (self.pageIndex - 1) + 2 - yPos or 2 - yPos --融合窗口中每页从第几个开始打印)
    local count = 1
    for i = index, index + listLen - 1, 1 do
        if not shipNet_list[i] then break end
        local s, id = shipNet_list[i].name, shipNet_list[i].id
        if #s > self.width - 2 then
            s = string.sub(s, 1, self.width - 2)
        end
        local x, y = 2, 2 + count
        count = count + 1
        if y > 2 then
            self.window.setCursorPos(x, y)
            local flagF, flagBg = font, bg

            if id == calling then --如果正在呼叫对方
                if self.callBlink % 2 == 0 then
                    flagF, flagBg = "f", select
                else
                    flagF, flagBg = select, bg
                end
                self.callBlink = self.callBlink - 0.25 > 0 and self.callBlink - 0.25 or 10
            elseif id == parentShip.id then --如果是父级飞船
                flagF, flagBg = bg, "d"
            else
                for k, v in pairs(childShips) do --如果是子级飞船
                    if table.contains(v, id) then
                        flagF, flagBg = bg, "b"
                        break
                    end
                end
            end

            self.window.blit(s, genStr(flagF, #s), genStr(flagBg, #s))
        end
    end
end

function shipNetPage:onTouch(x, y)
    local info = page_attach_manager:get(self.name, self.pageName, self.row, self.column)
    local width, height, xPos, yPos, maxRow
    if info ~= -1 then
        width = info.maxColumn * self.width
        height = info.maxRow * self.height
        xPos = (info.column - 1) * self.width + 1
        yPos = (info.row - 1) * self.height
        maxRow = info.maxRow
        if info.row == 1 then
            yPos = yPos + 1
            self:nextPage(x, y)
        end
    else
        width, height, xPos, yPos, maxRow = self.width, self.height, 1, 1, 1
        self:nextPage(x, y)
    end

    if x >= 2 and x <= self.width - 1 then
        local listLen = height - 5
        if #shipNet_list > listLen then --翻页键
            local maxPage = math.ceil(#shipNet_list / listLen)
            if y == 2 then
                self.pageIndex = self.pageIndex - 1
                self.pageIndex = self.pageIndex < 1 and maxPage or self.pageIndex
            elseif y == self.height - 2 then
                self.pageIndex = self.pageIndex + 1
                self.pageIndex = self.pageIndex > maxPage and 1 or self.pageIndex
            end
        end

        if self.row == maxRow or info == -1 then
            if y == self.height - 1 then
                if x >= 2 and x <= 2 + 3 then
                    properties.winIndex[self.name][self.row][self.column] = 15
                elseif x >= self.width - 7 then
                    properties.winIndex[self.name][self.row][self.column] = 16
                end
            end
        end

        if (self.pageIndex == 1 and (y > 2)) or (self.pageIndex > 1 and y > 1 and y < self.height - 2) then
            local index = #shipNet_list > listLen and listLen * (self.pageIndex - 1) + 2 - yPos or
                2 - yPos --融合窗口中每页从第几个开始打印
            index = y - 3 + index
            if shipNet_list[index] then
                shipNet_p2p_send(shipNet_list[index].id, "call")
            end
        end
    end
end

--winIndex = 16
function shipNet_set_Page:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 2
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,            blitB = bg },
        { text = "set_camera",    x = 2, y = 3, blitF = genStr(font, 10), blitB = genStr(bg, 10), select = genStr(select, 10), flag = false },
        { text = "set_follow",    x = 2, y = 4, blitF = genStr(font, 10), blitB = genStr(bg, 10), select = genStr(select, 10), flag = false },
        { text = "set_anchorage", x = 2, y = 5, blitF = genStr(font, 13), blitB = genStr(bg, 13), select = genStr(select, 13), flag = false },
    }
end

function shipNet_set_Page:refresh()
    self:refreshButtons()
    self:refreshTitle()
    for k, v in pairs(self.buttons) do
        if v.flag then
            self.window.setCursorPos(v.x, v.y)
            self.window.blit(v.text, v.blitB, v.select)
        end
    end
end

function shipNet_set_Page:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 1 and y <= #self.buttons + 1 then
        for k, v in pairs(self.buttons) do
            if y == v.y and x >= v.x and x <= v.x + #v.text then
                if v.flag then
                    if v.text == "set_camera" then
                        properties.winIndex[self.name][self.row][self.column] = 17
                    elseif v.text == "set_follow" then
                        properties.winIndex[self.name][self.row][self.column] = 18
                    elseif v.text == "set_anchorage" then
                        properties.winIndex[self.name][self.row][self.column] = 19
                    end
                else
                    v.flag = true
                end
            else
                v.flag = false
            end
        end
    end
end

--winIndex = 17
function shipNet_connect_Page:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 2
    self.buttons = {
        { text = "<", x = 1, y = 1, blitF = title, blitB = bg },
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 2, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.maxList = self.height - 3
    self.pageIndex = 1
end

function shipNet_connect_Page:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local list = {}
    if parentShip.id ~= -1 then
        table.insert(list, parentShip)
    end

    for k, v in pairs(childShips) do
        table.insert(list, v)
    end

    if #list > self.maxList then
        self.window.setCursorPos(2, self.height - 1)
        self.window.blit(self.otherButtons[2].text, self.otherButtons[2].blitF, self.otherButtons[2].blitB)
        if self.pageIndex == 1 then
            self.window.setCursorPos(2, 2)
            self.window.blit(self.otherButtons[1].text, self.otherButtons[1].blitF, self.otherButtons[1].blitB)
        end
    end

    for i = 1, self.maxList, 1 do
        local index = (self.pageIndex - 1) * self.maxList + i
        if not list[index] then
            break
        end
        local str = list[index].name
        if #str > self.width - 3 then
            str = string.sub(str, 1, self.width - 4)
        end
        str = str .. " x"
        self.window.setCursorPos(2, 2 + i)
        if list[index].id == parentShip.id and i == 1 then
            self.window.blit(str, genStr(properties.bg, #str - 1) .. properties.font,
                genStr("d", #str - 2) .. genStr(properties.bg, 2))
        else
            self.window.blit(str, genStr(properties.bg, #str - 1) .. properties.font,
                genStr("b", #str - 2) .. genStr(properties.bg, 2))
        end
    end

    if #callList > 0 then --收到请求弹窗
        local str = callList[1].name
        local halfWidth = self.width / 2
        local halfHeight = self.height / 2
        if #str < self.width then
            str = genStr(" ", halfWidth - math.floor(#str / 2 + 0.5)) ..
                str .. genStr(" ", halfWidth + math.floor(#str / 2 + 0.5))
        end
        self.window.setCursorPos(1, halfHeight - 1)
        self.window.blit(str, genStr(properties.bg, #str), genStr(properties.other, #str))
        local str2 = "connect? " .. callList[1].ct
        if #str2 < self.width then
            str2 = genStr(" ", halfWidth - math.floor(#str2 / 2 + 0.5)) ..
                str2 .. genStr(" ", halfWidth + math.floor(#str2 / 2 + 0.5))
        end
        self.window.setCursorPos(1, halfHeight)
        self.window.blit(str2, genStr(properties.bg, #str2), genStr(properties.other, #str2))
        self.window.setCursorPos(halfWidth - 4, halfHeight + 2)
        self.window.blit("yes", genStr(properties.bg, 3), genStr(properties.select, 3))
        self.window.setCursorPos(halfWidth + 4, halfHeight + 2)
        self.window.blit("no", genStr(properties.bg, 2), genStr(properties.select, 2))
    end
end

local accept_connect = function(ship, code)
    shipNet_p2p_send(ship.id, "agree", code)
    local flag = false
    for k, v in pairs(childShips) do
        if v.name == ship.name then
            childShips[k] = ship
            childShips[k].beat = beat_ct
            flag = true
            break
        end
    end

    if not flag then
        local newChild = ship
        newChild.beat = beat_ct
        table.insert(childShips, newChild)
    end

    table.remove(callList, 1)
end

function shipNet_connect_Page:onTouch(x, y)
    self:subPage_Back(x, y)
    if parentShip.id ~= -1 then
        self.window.setCursorPos(2, 3)
    end
    if #callList > 0 then --收到请求弹窗
        local halfWidth = self.width / 2
        local halfHeight = self.height / 2
        
        if y == math.floor(halfHeight + 2) then
            if x >= halfWidth - 4 and x < halfWidth - 1 then
                table.insert(properties.shipNet_whiteList, callList[1].name)
                accept_connect(callList[1], callList[1].code)
            elseif x >= halfWidth + 4 and x <= halfWidth + 6 then
                shipNet_p2p_send(callList[1].id, "refuse")
                table.remove(callList, 1)
            end
        end
    else
        local list = {}
        if parentShip.id ~= -1 then
            table.insert(list, parentShip)
        end

        for k, v in pairs(childShips) do
            table.insert(list, v)
        end
        if #list > self.maxList then
            local maxPage = math.ceil(#list / self.maxList)
            if self.pageIndex < maxPage then
                if y == self.height - 1 and x > 1 then
                    self.pageIndex = self.pageIndex + 1 > maxPage and 1 or self.pageIndex + 1
                end
            end
            if self.pageIndex > 1 then
                if y == 2 and x > 1 then
                    self.pageIndex = self.pageIndex - 1 > 1 and maxPage or self.pageIndex - 1
                end
            end
        end

        for i = 1, self.maxList, 1 do
            local index = (self.pageIndex - 1) * self.maxList + i
            if not list[index] then
                break
            end
            local str = list[index].name
            if #str > self.width - 3 then
                str = string.sub(str, 1, self.width - 4)
            end
            str = str .. " x"
            if y == 2 + i and x >= #str - 1 and x <= #str + 1 then
                local i2 = i
                if parentShip.id ~= -1 then
                    if i == 1 then
                        parentShip.id = -1
                        properties.lastParent = -1
                        break
                    end
                    i2 = i2 - 1
                end
                for k, v in pairs(properties.shipNet_whiteList) do
                    if v == childShips[i2].name then
                        table.remove(properties.shipNet_whiteList, k)
                        break
                    end
                end
                table.remove(childShips, i2)
                break
            end
        end
    end
end

--winIndex = 18
function set_camera:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 15
    self.buttons = {
        { text = "<",              x = 1, y = 1, blitF = title,                      blitB = bg },
        { text = "rotSpeed -   +", x = 2, y = 3, blitF = genStr(font, 9) .. "fffff", blitB = genStr(bg, 9) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "moveSpeed-   +", x = 2, y = 5, blitF = genStr(font, 9) .. "fffff", blitB = genStr(bg, 9) .. "b" .. genStr(bg, 3) .. "e" },
    }
end

function set_camera:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(12, self.buttons[2].y)
    self.window.blit(string.format("%0.1f", profile.camera_rot_speed), genStr(properties.font, 3),
        genStr(properties.bg, 3))
    self.window.setCursorPos(12, self.buttons[3].y)
    self.window.blit(string.format("%0.1f", profile.camera_move_speed), genStr(properties.font, 3),
        genStr(properties.bg, 3))
end

function set_camera:onTouch(x, y)
    self:subPage_Back(x, y)
    local profile = properties.profile[properties.profileIndex]
    if y == self.buttons[2].y then
        if x == 11 then
            profile.camera_rot_speed = profile.camera_rot_speed - 0.1 < 0 and 0.1 or profile.camera_rot_speed - 0.1
        elseif x == 15 then
            profile.camera_rot_speed = profile.camera_rot_speed + 0.1 > 1 and 1 or profile.camera_rot_speed + 0.1
        end
    elseif y == self.buttons[3].y then
        if x == 11 then
            profile.camera_move_speed = profile.camera_move_speed - 0.1 < 0 and 0.1 or profile.camera_move_speed - 0.1
        elseif x == 15 then
            profile.camera_move_speed = profile.camera_move_speed + 0.1 > 1 and 1 or profile.camera_move_speed + 0.1
        end
    end
end
--winIndex = 26
function set_followRange:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 15
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "xOffset-    +", x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "yOffset-    +", x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "zOffset-    +", x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
    }
end
function set_followRange:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local sp = split(
        string.format("%d, %d, %d", properties.followRange.x, properties.followRange.y,
            properties.followRange.z), ", ")
    local sX, sY, sZ = sp[1], sp[2], sp[3]
    self.window.setCursorPos(11, self.buttons[2].y)
    self.window.blit(string.format("%d", sX), genStr(properties.font, #sX), genStr(properties.bg, #sX))
    self.window.setCursorPos(11, self.buttons[3].y)
    self.window.blit(string.format("%d", sY), genStr(properties.font, #sY), genStr(properties.bg, #sY))
    self.window.setCursorPos(11, self.buttons[4].y)
    self.window.blit(string.format("%d", sZ), genStr(properties.font, #sZ), genStr(properties.bg, #sZ))
end

function set_followRange:onTouch(x, y)
    self:subPage_Back(x, y)
    if y >= self.buttons[2].y and y <= self.buttons[4].y then
        local result = 0
        if x == 9 then
            result = -1
        elseif x == 14 then
            result = 1
        end
        if y == self.buttons[2].y then
            properties.followRange.x = properties.followRange.x + result
        elseif y == self.buttons[3].y then
            properties.followRange.y = properties.followRange.y + result
        elseif y == self.buttons[4].y then
            properties.followRange.z = properties.followRange.z + result
        end
    end
end


function set_shipFollow:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 15
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,  blitB = bg },
        { text = "x:--       ++", x = 2, y = 3, blitF = genStr(font, 2) .. genStr("f", 11), blitB = genStr(bg, 2) .. "b5" .. genStr(bg, 7) .. "1e" },
        { text = "y:--       ++", x = 2, y = 4, blitF = genStr(font, 2) .. genStr("f", 11), blitB = genStr(bg, 2) .. "b5" .. genStr(bg, 7) .. "1e" },
        { text = "z:--       ++", x = 2, y = 5, blitF = genStr(font, 2) .. genStr("f", 11), blitB = genStr(bg, 2) .. "b5" .. genStr(bg, 7) .. "1e" },
        { text = " roll--    ++", x = 2, y = 7, blitF = genStr(font, 5) .. genStr("f", 8), blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "  yaw--    ++", x = 2, y = 8, blitF = genStr(font, 5) .. genStr("f", 8), blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "pitch--    ++", x = 2, y = 9, blitF = genStr(font, 5) .. genStr("f", 8), blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
    }
end

function set_shipFollow:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local sp = split(
        string.format("%d, %d, %d, %d, %d, %d", properties.shipFollow_offset.x, properties.shipFollow_offset.y,
            properties.shipFollow_offset.z, properties.shipFollow_rotation.roll, properties.shipFollow_rotation.yaw, properties.shipFollow_rotation.pitch), ", ")
    local sX, sY, sZ = sp[1], sp[2], sp[3]
    self.window.setCursorPos(2, 6)
    if properties.shipFollow_use_custom_rotation then
        self.window.blit("use_custom_rot", genStr(properties.bg, 14), genStr(properties.select, 14))
    else
        self.window.blit("use_custom_rot", genStr(properties.font, 14), genStr(properties.bg, 14))
    end
    self.window.setCursorPos(7, self.buttons[2].y)
    self.window.blit(string.format("%d", sX), genStr(properties.font, #sX), genStr(properties.bg, #sX))
    self.window.setCursorPos(7, self.buttons[3].y)
    self.window.blit(string.format("%d", sY), genStr(properties.font, #sY), genStr(properties.bg, #sY))
    self.window.setCursorPos(7, self.buttons[4].y)
    self.window.blit(string.format("%d", sZ), genStr(properties.font, #sZ), genStr(properties.bg, #sZ))
    local sRoll, sYaw, sPitch = sp[4], sp[5], sp[6]
    self.window.setCursorPos(9, self.buttons[5].y)
    self.window.blit(string.format("%d", sRoll), genStr(properties.font, #sRoll), genStr(properties.bg, #sRoll))
    self.window.setCursorPos(9, self.buttons[6].y)
    self.window.blit(string.format("%d", sYaw), genStr(properties.font, #sYaw), genStr(properties.bg, #sYaw))
    self.window.setCursorPos(9, self.buttons[7].y)
    self.window.blit(string.format("%d", sPitch), genStr(properties.font, #sPitch), genStr(properties.bg, #sPitch))
end

function set_shipFollow:onTouch(x, y)
    self:subPage_Back(x, y)
    if y >= self.buttons[2].y and y <= self.buttons[4].y then
        local result = 0
        if x == 4 then result = -10
        elseif x == 5 then result = -1
        elseif x == 13 then result = 1
        elseif x == 14 then result = 10
        end
        if y == self.buttons[2].y then
            properties.shipFollow_offset.x = properties.shipFollow_offset.x + result
        elseif y == self.buttons[3].y then
            properties.shipFollow_offset.y = properties.shipFollow_offset.y + result
        elseif y == self.buttons[4].y then
            properties.shipFollow_offset.z = properties.shipFollow_offset.z + result
        end
    elseif y == 6 then
        properties.shipFollow_use_custom_rotation = not properties.shipFollow_use_custom_rotation
    elseif y >= self.buttons[5].y and y <= self.buttons[7].y then
        local result = 0
        if x == 7 then result = -10
        elseif x == 8 then result = -1
        elseif x == 13 then result = 1
        elseif x == 14 then result = 10
        end
        if y == self.buttons[5].y then
            properties.shipFollow_rotation.roll = resetAngelRangeDeg(properties.shipFollow_rotation.roll + result)
        elseif y == self.buttons[6].y then
            properties.shipFollow_rotation.yaw = resetAngelRangeDeg(properties.shipFollow_rotation.yaw + result)
        elseif y == self.buttons[7].y then
            properties.shipFollow_rotation.pitch = resetAngelRangeDeg(properties.shipFollow_rotation.pitch + result)
        end
    end
end

--winIndex = 20
function set_anchorage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 16
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "xOffset-    +", x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "yOffset-    +", x = 2, y = 4, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "zOffset-    +", x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "entry:",        x = 2, y = 7, blitF = genStr(font, 6),             blitB = genStr(bg, 6) },
        { text = "<         >",   x = 3, y = 8, blitF = genStr(font, 11),            blitB = genStr(bg, 11) },
    }
end

function set_anchorage:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local tmpX = string.format("%d", properties.anchorage_offset.x)
    self.window.setCursorPos(11, self.buttons[2].y)
    self.window.blit(tmpX, genStr(properties.font, #tmpX), genStr(properties.bg, #tmpX))
    local tmpY = string.format("%d", properties.anchorage_offset.y)
    self.window.setCursorPos(11, self.buttons[3].y)
    self.window.blit(tmpY, genStr(properties.font, #tmpY), genStr(properties.bg, #tmpY))
    local tmpZ = string.format("%d", properties.anchorage_offset.z)
    self.window.setCursorPos(11, self.buttons[4].y)
    self.window.blit(tmpZ, genStr(properties.font, #tmpZ), genStr(properties.bg, #tmpZ))

    local ent = entryList[properties.anchorage_entry]
    self.window.setCursorPos(6, self.buttons[6].y)
    self.window.blit(ent, genStr(properties.font, #ent), genStr(properties.bg, #ent))
end

function set_anchorage:onTouch(x, y)
    self:subPage_Back(x, y)
    if y >= self.buttons[2].y and y <= self.buttons[4].y then
        local result = 0
        if x == 9 then
            result = -1
        elseif x == 14 then
            result = 1
        end
        if y == self.buttons[2].y then
            properties.anchorage_offset.x = properties.anchorage_offset.x + result
        elseif y == self.buttons[3].y then
            properties.anchorage_offset.y = properties.anchorage_offset.y + result
        elseif y == self.buttons[4].y then
            properties.anchorage_offset.z = properties.anchorage_offset.z + result
        end
    elseif y == self.buttons[6].y then
        local result = 0
        if x == 3 then
            result = -1
        elseif x == 13 then
            result = 1
        end
        properties.anchorage_entry = properties.anchorage_entry + result
        properties.anchorage_entry = properties.anchorage_entry > #entryList and 1 or properties.anchorage_entry
        properties.anchorage_entry = properties.anchorage_entry < 1 and #entryList or properties.anchorage_entry
    end
end

--winIndex = 5
function setPage:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.buttons = {
        { text = "<    SET    >", x = self.width / 2 - 5, y = 1,       blitF = genStr(title, 13), blitB = genStr(bg, 13) },
        { text = "S_SpaceShip",   x = 2,                  pageId = 5,  y = 3,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_QuadFPV  ",   x = 2,                  pageId = 6,  y = 4,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_FixedWing",   x = 2,                  pageId = 24, y = 5,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_Helicopt ",   x = 2,                  pageId = 7,  y = 6,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "S_airShip  ",   x = 2,                  pageId = 8,  y = 7,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "User_Change",   x = 2,                  pageId = 9, y = 8,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Home_Set   ",   x = 2,                  pageId = 10, y = 9,                     blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "FollowRange",   x = 2,                  pageId = 25, y = 10,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Simulate   ",   x = 2,                  pageId = 11, y = 11,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Set_Other  ",   x = 2,                  pageId = 12, y = 12,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Profile    ",   x = 2,                  pageId = 13, y = 13,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "Colortheme ",   x = 2,                  pageId = 14, y = 14,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false },
        { text = "MassFix",       x = 2,                  pageId = 20, y = 15,                    blitF = genStr(font, 7),  blitB = genStr(bg, 7),  select = genStr(select, 7),  selected = false, flag = false },
        { text = "Recordings ",   x = 2,                  pageId = 26, y = 16,                    blitF = genStr(font, 11), blitB = genStr(bg, 11), select = genStr(select, 11), selected = false, flag = false }
    }
    self.otherButtons = {
        { text = "      v      ", x = 2, y = self.height - 1, blitF = genStr(bg, 13), blitB = genStr(other, 13) },
        { text = "      ^      ", x = 2, y = 2,               blitF = genStr(bg, 13), blitB = genStr(other, 13) },
    }
    self.pageIndex = 1
    self.cutRow = 4
end

function setPage:refresh()
    self:refreshButtons(1, self.pageIndex, self.cutRow)
    if #self.buttons > self.height - self.cutRow then
        if self.pageIndex == 1 or self.pageIndex * (self.height - self.cutRow) < #self.buttons - 1 then
            local bt = self.otherButtons[1]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
        if self.pageIndex > 1 then
            local bt = self.otherButtons[2]
            self.window.setCursorPos(bt.x, bt.y)
            self.window.blit(bt.text, bt.blitF, bt.blitB)
        end
    end
    for k, v in pairs(self.buttons) do
        if v.selected then
            local yPos = v.y - (self.pageIndex - 1) * (self.height - self.cutRow)
            if yPos > 2 and yPos < self.height - 1 then
                self.window.setCursorPos(v.x, v.y - (self.pageIndex - 1) * (self.height - self.cutRow))
                self.window.blit(v.text, v.blitB, v.select)
            end
        end
    end
end

function setPage:onTouch(x, y)
    self:nextPage(x, y)
    if y == 2 then
        if self.pageIndex > 1 then
            self.pageIndex = self.pageIndex - 1
        end
    elseif y < self.height - 1 and y > 2 then
        for k, v in pairs(self.buttons) do
            if v.y > 1 then
                if x >= v.x and x < v.x + #v.text and y == v.y - (self.pageIndex - 1) * (self.height - 4) then
                    if not v.selected then
                        v.selected = true
                    else
                        self.windows[self.row][self.column][v.pageId].indexFlag = 4
                        properties.winIndex[self.name][self.row][self.column] = v.pageId
                    end
                else
                    v.selected = false
                end
            end
        end
    elseif y == self.otherButtons[1].y then
        if #self.buttons - 1 > self.pageIndex * (self.height - 4) then
            self.pageIndex = self.pageIndex + 1
        else
            self.pageIndex = 1
        end
    end
end

--winIndex = 5
function set_spaceShip:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                           blitB = bg },
        { text = "P: --      ++", x = 2, y = 3, blitF = genStr(font, 3) .. "ffffffffff", blitB = genStr(bg, 3) .. "b5" .. genStr(bg, 6) .. "1e" },
        { text = "D: --      ++", x = 2, y = 4, blitF = genStr(font, 3) .. "ffffffffff", blitB = genStr(bg, 3) .. "b5" .. genStr(bg, 6) .. "1e" },
        { text = "Forward -   +", x = 2, y = 5, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "vertMove-   +", x = 2, y = 6, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "SideMove-   +", x = 2, y = 7, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "MOVE_D: -   +", x = 2, y = 8, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "Burner: -   +", x = 2, y = 9, blitF = genStr(font, 8) .. "fffff",      blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" }
    }
end

function set_spaceShip:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(8, 3)
    self.window.write(string.format("%0.2f", profile.spaceShip_P))
    self.window.setCursorPos(8, 4)
    self.window.write(string.format("%0.2f", profile.spaceShip_D))
    self.window.setCursorPos(11, 5)
    self.window.write(string.format("%0.1f", profile.spaceShip_forward))
    self.window.setCursorPos(11, 6)
    self.window.write(string.format("%0.1f", profile.spaceShip_vertMove))
    self.window.setCursorPos(11, 7)
    self.window.write(string.format("%0.1f", profile.spaceShip_sideMove))
    self.window.setCursorPos(11, 8)
    self.window.write(string.format("%0.1f", profile.spaceShip_move_D))
    self.window.setCursorPos(11, 9)
    self.window.write(string.format("%0.1f", profile.spaceShip_burner))
end

function set_spaceShip:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 5
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if y == 3 or y == 4 then
            if x == 5 then result = -0.1 end
            if x == 6 then result = -0.01 end
            if x == 13 then result = 0.01 end
            if x == 14 then result = 0.1 end
            if y == 3 then
                profile.spaceShip_P = profile.spaceShip_P + result
                profile.spaceShip_P = profile.spaceShip_P < 0 and 0 or profile.spaceShip_P
            elseif y == 4 then
                profile.spaceShip_D = profile.spaceShip_D + result
            end
        elseif y > 4 then
            if x == 10 then result = -0.1 end
            if x == 14 then result = 0.1 end
            if y == 5 then
                profile.spaceShip_forward = profile.spaceShip_forward + result < 0 and 0 or profile.spaceShip_forward + result
            elseif y == 6 then
                profile.spaceShip_vertMove = profile.spaceShip_vertMove + result < 0 and 0 or profile.spaceShip_vertMove + result
            elseif y == 7 then
                profile.spaceShip_sideMove = profile.spaceShip_sideMove + result < 0 and 0 or profile.spaceShip_sideMove + result
            elseif y == 8 then
                profile.spaceShip_move_D = profile.spaceShip_move_D + result < 0 and 0 or profile.spaceShip_move_D + result
            elseif y == 9 then
                profile.spaceShip_burner = profile.spaceShip_burner + result < 0 and 0 or profile.spaceShip_burner + result
            end
        end
    end
end

--winIndex = 7
function set_quadFPV:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "Rate_Roll >",   x = 2, y = 3, blitF = genStr(font, 11),            blitB = genStr(bg, 11),                              select = genStr(select, 11), selected = false, flag = false },
        { text = "Rate_Yaw  >",   x = 2, y = 4, blitF = genStr(font, 11),            blitB = genStr(bg, 11),                              select = genStr(select, 11), selected = false, flag = false },
        { text = "Rate_Pitch>",   x = 2, y = 5, blitF = genStr(font, 11),            blitB = genStr(bg, 11),                              select = genStr(select, 11), selected = false, flag = false },

        { text = "Throttle:",     x = 2, y = 6, blitF = genStr(other, 9),            blitB = genStr(bg, 9) },
        { text = "max_val-    +", x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "mid    -    +", x = 2, y = 8, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +", x = 2, y = 9, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
    }
end

function set_quadFPV:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))

    for k, v in pairs(self.buttons) do
        if v.selected then
            self.window.setCursorPos(v.x, v.y)
            self.window.blit(v.text, v.blitB, v.select)
        end
    end

    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.max_throttle))
    self.window.setCursorPos(10, 8)
    self.window.write(string.format("%0.2f", profile.throttle_mid))
    self.window.setCursorPos(10, 9)
    self.window.write(string.format("%0.2f", profile.throttle_expo))
end

function set_quadFPV:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 6
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if y > 6 and y < 10 then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 7 then
                profile.max_throttle = profile.max_throttle + result
                profile.max_throttle = profile.max_throttle < 0 and 0 or
                    (profile.max_throttle > 9 and 9 or profile.max_throttle)
            elseif y == 8 then
                profile.throttle_mid = profile.throttle_mid + result
                profile.throttle_mid = profile.throttle_mid < 0 and 0 or
                    (profile.throttle_mid > 1 and 1 or profile.throttle_mid)
            elseif y == 9 then
                profile.throttle_expo = profile.throttle_expo + result
                profile.throttle_expo = profile.throttle_expo < 0 and 0 or
                    (profile.throttle_expo > 1 and 1 or profile.throttle_expo)
            end
        end
    end
    for k, v in pairs(self.buttons) do
        if v.selected ~= nil then
            if x >= v.x and x < v.x + #v.text and y == v.y then
                if not v.selected then
                    v.selected = true
                else
                    if v.text == "Rate_Roll >" then
                        self.windows[self.row][self.column][21].indexFlag = 6
                        properties.winIndex[self.name][self.row][self.column] = 21
                    elseif v.text == "Rate_Yaw  >" then
                        self.windows[self.row][self.column][22].indexFlag = 6
                        properties.winIndex[self.name][self.row][self.column] = 22
                    elseif v.text == "Rate_Pitch>" then
                        self.windows[self.row][self.column][23].indexFlag = 6
                        properties.winIndex[self.name][self.row][self.column] = 23
                    end
                end
            else
                v.selected = false
            end
        end
    end
end

--winIndex = 25
function set_fixedWing:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "< wingOffset",   x = 1, y = 2, blitF = genStr(title, 12),             blitB = genStr(bg, 12) },
        { text = "Wing:-   +",     x = 1, y = 3, blitF = genStr(font, 5) .. "fffff",    blitB = genStr(bg, 5) .. "b" .. "fff" .. "e" },
        { text = "wSize:--    ++", x = 1, y = 4, blitF = genStr(font, 6) .. "ffffffff", blitB = genStr(bg, 6) .. "b5" .. "ffff" .. "1e" },
        { text = "Tail:-   +",     x = 1, y = 5, blitF = genStr(font, 5) .. "fffff",    blitB = genStr(bg, 5) .. "b" .. "fff" .. "e" },
        { text = "tSize:--    ++", x = 1, y = 6, blitF = genStr(font, 6) .. "ffffffff", blitB = genStr(bg, 6) .. "b5" .. "ffff" .. "1e" },
        { text = "vertTail:-   +", x = 1, y = 7, blitF = genStr(font, 9) .. "fffff",    blitB = genStr(bg, 9) .. "b" .. "fff" .. "e" },
        { text = "vSize:--    ++", x = 1, y = 8, blitF = genStr(font, 6) .. "ffffffff", blitB = genStr(bg, 6) .. "b5" .. "ffff" .. "1e" },
    }
end

function set_fixedWing:refresh()
    self:refreshButtons()
    self:refreshTitle()
    self.window.setCursorPos(7, 3)
    self.window.write(math.floor(properties.wing.wings.pos.x + 0.5))
    self.window.setCursorPos(9, 4)
    self.window.write(string.format("%0.2f", properties.wing.wings.size))
    self.window.setCursorPos(7, 5)
    self.window.write(math.floor(properties.wing.tail_wings.pos.x + 0.5))
    self.window.setCursorPos(9, 6)
    self.window.write(string.format("%0.2f", properties.wing.tail_wings.size))
    self.window.setCursorPos(11, 7)
    self.window.write(math.floor(properties.wing.verticalTail.pos.x + 0.5))
    self.window.setCursorPos(9, 8)
    self.window.write(string.format("%0.2f", properties.wing.verticalTail.size))
end

function set_fixedWing:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 3 or y == 5 or y == 7 then
        local result = 0
        if y < 7 then
            if x == 6 then
                result = -1
            elseif x == 10 then
                result = 1
            end
        else
            if x == 10 then
                result = -1
            elseif x == 14 then
                result = 1
            end
        end

        if y == 3 then
            properties.wing.wings.pos.x = properties.wing.wings.pos.x + result
        elseif y == 5 then
            properties.wing.tail_wings.pos.x = properties.wing.tail_wings.pos.x + result
        elseif y == 7 then
            properties.wing.verticalTail.pos.x = properties.wing.verticalTail.pos.x + result
        end
    elseif y == 4 or y == 6 or y == 8 then
        local result = 0
        if x == 7 then
            result = -0.1
        elseif x == 8 then
            result = -0.01
        elseif x == 13 then
            result = 0.01
        elseif x == 14 then
            result = 0.1
        end
        if y == 4 then
            properties.wing.wings.size = properties.wing.wings.size + result
            properties.wing.wings.size = properties.wing.wings.size < 0.01 and 0.01 or properties.wing.wings.size
        elseif y == 6 then
            properties.wing.tail_wings.size = properties.wing.tail_wings.size + result
            properties.wing.tail_wings.size = properties.wing.tail_wings.size < 0.01 and 0.01 or
                properties.wing.tail_wings.size
        elseif y == 8 then
            properties.wing.verticalTail.size = properties.wing.verticalTail.size + result
            properties.wing.verticalTail.size = properties.wing.verticalTail.size < 0.01 and 0.01 or
                properties.wing.verticalTail.size
        end
    end
end

--winIndex = 22
function rate_Roll:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 7
    self.buttons = {
        { text = "<",               x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "rc_Rate-    +",   x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "s_Rate -    +",   x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +",   x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "maxDeg:     d/s", x = 1, y = 9, blitF = genStr(font, 15),            blitB = genStr(bg, 15) },
    }
end

function rate_Roll:refresh()
    self:refreshButtons()
    self:refreshTitle()

    local profile = properties.profile[properties.profileIndex]
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 3)
    self.window.write(string.format("%0.2f", profile.roll_rc_rate))
    self.window.setCursorPos(10, 5)
    self.window.write(string.format("%0.2f", profile.roll_s_rate))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.roll_expo))
    self.window.setCursorPos(8, 9)
    self.window.write(string.format("%d", getRate(profile.roll_rc_rate, profile.roll_s_rate, profile.roll_expo, 1.0)))
end

function rate_Roll:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or (y > 6 and y < 10) then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 3 then
                profile.roll_rc_rate = profile.roll_rc_rate + result
                profile.roll_rc_rate = profile.roll_rc_rate < 0 and 0 or
                    (profile.roll_rc_rate > 2.55 and 2.55 or profile.roll_rc_rate)
            elseif y == 5 then
                profile.roll_s_rate = profile.roll_s_rate + result
                profile.roll_s_rate = profile.roll_s_rate < 0 and 0 or
                    (profile.roll_s_rate > 1 and 1 or profile.roll_s_rate)
            elseif y == 7 then
                profile.roll_expo = profile.roll_expo + result
                profile.roll_expo = profile.roll_expo < 0 and 0 or (profile.roll_expo > 1 and 1 or profile.roll_expo)
            end
        end
    end
end

--winIndex = 23
function rate_Yaw:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 7
    self.buttons = {
        { text = "<",               x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "rc_Rate-    +",   x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "s_Rate -    +",   x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +",   x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "maxDeg:     d/s", x = 1, y = 9, blitF = genStr(font, 15),            blitB = genStr(bg, 15) },
    }
end

function rate_Yaw:refresh()
    self:refreshButtons()
    self:refreshTitle()

    local profile = properties.profile[properties.profileIndex]
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 3)
    self.window.write(string.format("%0.2f", profile.yaw_rc_rate))
    self.window.setCursorPos(10, 5)
    self.window.write(string.format("%0.2f", profile.yaw_s_rate))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.yaw_expo))
    self.window.setCursorPos(8, 9)
    self.window.write(string.format("%d", getRate(profile.yaw_rc_rate, profile.yaw_s_rate, profile.yaw_expo, 1.0)))
end

function rate_Yaw:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or (y > 6 and y < 10) then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 3 then
                profile.yaw_rc_rate = profile.yaw_rc_rate + result
                profile.yaw_rc_rate = profile.yaw_rc_rate < 0 and 0 or
                    (profile.yaw_rc_rate > 2.55 and 2.55 or profile.yaw_rc_rate)
            elseif y == 5 then
                profile.yaw_s_rate = profile.yaw_s_rate + result
                profile.yaw_s_rate = profile.yaw_s_rate < 0 and 0 or
                    (profile.yaw_s_rate > 1 and 1 or profile.yaw_s_rate)
            elseif y == 7 then
                profile.yaw_expo = profile.yaw_expo + result
                profile.yaw_expo = profile.yaw_expo < 0 and 0 or (profile.yaw_expo > 1 and 1 or profile.yaw_expo)
            end
        end
    end
end

--winIndex = 24
function rate_Pitch:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 7
    self.buttons = {
        { text = "<",               x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "rc_Rate-    +",   x = 2, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "s_Rate -    +",   x = 2, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "expo   -    +",   x = 2, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "maxDeg:     d/s", x = 1, y = 9, blitF = genStr(font, 15),            blitB = genStr(bg, 15) },
    }
end

function rate_Pitch:refresh()
    self:refreshButtons()
    self:refreshTitle()

    local profile = properties.profile[properties.profileIndex]
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(10, 3)
    self.window.write(string.format("%0.2f", profile.pitch_rc_rate))
    self.window.setCursorPos(10, 5)
    self.window.write(string.format("%0.2f", profile.pitch_s_rate))
    self.window.setCursorPos(10, 7)
    self.window.write(string.format("%0.2f", profile.pitch_expo))
    self.window.setCursorPos(8, 9)
    self.window.write(string.format("%d", getRate(profile.pitch_rc_rate, profile.pitch_s_rate, profile.pitch_expo, 1.0)))
end

function rate_Pitch:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or (y > 6 and y < 10) then
            if x == 9 then result = -0.01 end
            if x == 14 then result = 0.01 end
            if y == 3 then
                profile.pitch_rc_rate = profile.pitch_rc_rate + result
                profile.pitch_rc_rate = profile.pitch_rc_rate < 0 and 0 or
                    (profile.pitch_rc_rate > 2.55 and 2.55 or profile.pitch_rc_rate)
            elseif y == 5 then
                profile.pitch_s_rate = profile.pitch_s_rate + result
                profile.pitch_s_rate = profile.pitch_s_rate < 0 and 0 or
                    (profile.pitch_s_rate > 1 and 1 or profile.pitch_s_rate)
            elseif y == 7 then
                profile.pitch_expo = profile.pitch_expo + result
                profile.pitch_expo = profile.pitch_expo < 0 and 0 or (profile.pitch_expo > 1 and 1 or profile.pitch_expo)
            end
        end
    end
end

--winIndex = 8
function set_helicopter:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                         blitB = bg },
        { text = "Rot_P--    ++", x = 2, y = 3, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "Rot_D--    ++", x = 2, y = 4, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "ACC:-   +",     x = 2, y = 6, blitF = genStr(font, 4) .. "fffff",    blitB = genStr(bg, 4) .. "b" .. genStr(bg, 3) .. "e" },
        { text = "Acc_D--    ++", x = 2, y = 7, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "MaxAngle:-  +", x = 2, y = 9, blitF = genStr(font, 9) .. "ffff",     blitB = genStr(bg, 9) .. "b" .. genStr(bg, 2) .. "e" }
    }
end

function set_helicopter:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(9, 3)
    self.window.write(string.format("%0.2f", profile.helicopt_ROT_P))
    self.window.setCursorPos(9, 4)
    self.window.write(string.format("%0.2f", profile.helicopt_ROT_D))
    self.window.setCursorPos(7, 6)
    self.window.write(string.format("%0.1f", profile.helicopt_ACC))
    self.window.setCursorPos(9, 7)
    self.window.write(string.format("%0.2f", profile.helicopt_ACC_D))
    self.window.setCursorPos(12, 9)
    self.window.write(string.format("%d", profile.helicopt_MAX_ANGLE))
end

function set_helicopter:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 7
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if (y > 2 and y < 6) or y == 7 then
            if x == 7 then result = -0.1 end
            if x == 8 then result = -0.01 end
            if x == 13 then result = 0.01 end
            if x == 14 then result = 0.1 end
            if y == 3 then
                profile.helicopt_ROT_P = profile.helicopt_ROT_P + result < 0 and 0 or profile.helicopt_ROT_P + result
            elseif y == 4 then
                profile.helicopt_ROT_D = profile.helicopt_ROT_D + result < 0 and 0 or profile.helicopt_ROT_D + result
            elseif y == 7 then
                profile.helicopt_ACC_D = profile.helicopt_ACC_D + result < 0 and 0 or profile.helicopt_ACC_D + result
            end
        elseif y == 6 then
            if x == 6 then result = -0.1 end
            if x == 10 then result = 0.1 end
            profile.helicopt_ACC = profile.helicopt_ACC + result < 0 and 0 or profile.helicopt_ACC + result
        elseif y == 9 then
            if x == 11 then result = -1 end
            if x == 14 then result = 1 end
            profile.helicopt_MAX_ANGLE = profile.helicopt_MAX_ANGLE + result < 0 and 0 or
                profile.helicopt_MAX_ANGLE + result
            profile.helicopt_MAX_ANGLE = profile.helicopt_MAX_ANGLE > 90 and 90 or profile.helicopt_MAX_ANGLE
        end
    end
end

--winIndex = 9
function set_airShip:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                         blitB = bg },
        { text = "Rot_P--    ++", x = 2, y = 3, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "Rot_D--    ++", x = 2, y = 4, blitF = genStr(font, 5) .. "ffffffff", blitB = genStr(bg, 5) .. "b5" .. genStr(bg, 4) .. "1e" },
        { text = "MOVE_P: -   +", x = 2, y = 6, blitF = genStr(font, 8) .. "fffff",    blitB = genStr(bg, 8) .. "b" .. genStr(bg, 3) .. "e" }
    }
end

function set_airShip:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local profile = properties.profile[properties.profileIndex]
    self.window.setCursorPos(1, 2)
    self.window.blit(("profile:%s"):format(properties.profileIndex), genStr(properties.other, 16),
        genStr(properties.bg, 16))
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(9, 3)
    self.window.write(string.format("%0.2f", profile.airShip_ROT_P))
    self.window.setCursorPos(9, 4)
    self.window.write(string.format("%0.2f", profile.airShip_ROT_D))
    self.window.setCursorPos(11, 6)
    self.window.write(string.format("%0.1f", profile.airShip_MOVE_P))
end

function set_airShip:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 2 then
        self.windows[self.row][self.column][14].indexFlag = 8
        properties.winIndex[self.name][self.row][self.column] = 13
    end
    if x > 2 and y > 2 then
        local profile = properties.profile[properties.profileIndex]
        local result = 0
        if y == 3 or y == 4 then
            if x == 7 then result = -0.1 end
            if x == 8 then result = -0.01 end
            if x == 13 then result = 0.01 end
            if x == 14 then result = 0.1 end
            if y == 3 then
                profile.airShip_ROT_P = profile.airShip_ROT_P + result < 0 and 0 or profile.airShip_ROT_P + result
            else
                profile.airShip_ROT_D = profile.airShip_ROT_D + result < 0 and 0 or profile.airShip_ROT_D + result
            end
        elseif y == 6 then
            if x == 10 then result = -0.1 end
            if x == 14 then result = 0.1 end
            profile.airShip_MOVE_P = profile.airShip_MOVE_P + result < 0 and 0 or profile.airShip_MOVE_P + result
        end
    end
end

--winIndex = 10
function set_user:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",           x = 1, y = 1, blitF = title,             blitB = bg },
        { text = "selectUser:", x = 2, y = 2, blitF = genStr(other, 11), blitB = genStr(bg, 11) },
    }
end

function set_user:refresh()
    scanner:getPlayer(properties.radarRange)
    self:refreshButtons()
    self:refreshTitle()
    local bg, font, select = properties.bg, properties.font, properties.select
    local index = 1
    for k, v in pairs(scanner.players) do
        if index == 7 then break end
        self.window.setCursorPos(2, 2 + index)
        if v.name == properties.userName then
            self.window.blit(v.name, genStr(bg, #v.name), genStr(select, #v.name))
        else
            self.window.blit(v.name, genStr(font, #v.name), genStr(bg, #v.name))
        end
        index = index + 1
    end
end

function set_user:onTouch(x, y)
    self:subPage_Back(x, y)
    if y > 2 and y < 10 then
        local user = scanner.players[y - 2]
        if user then
            properties.userName = user.name
        end
    end
end

--winIndex = 11
function set_home:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<", x = 1, y = 1, blitF = title, blitB = bg }
    }
end

function set_home:refresh()
    self:refreshButtons()
    self:refreshTitle()
end

function set_home:onTouch(x, y)
    self:subPage_Back(x, y)
end

local context_pool = {}
function context_pool:refresh(list, target)
    local font, bg, other, select = properties.font, properties.bg, properties.other, properties.select
    self.list = list
    local max_n = self.height - 2
    self.max_page = math.ceil(#self.list / max_n)
    local startPoint = self.pageIndex == 1 and 0 or (self.pageIndex - 1) * max_n
    for i = 1, max_n, 1 do
        local str = self.list[startPoint + i]
        if str then
            local fStr = #str > self.width and str:sub(1, self.width) or str
            self.window.setCursorPos(self.x, self.y + i)
            if str == self.target then
                self.window.blit(fStr, genStr(bg, #fStr), genStr(select, #fStr))
            else
                self.window.blit(fStr, genStr(font, #fStr), genStr(bg, #fStr))
            end
        else
            break
        end
    end

    if self.pageIndex == 1 and self.max_page > 1 then
        self.window.setCursorPos(self.x, self.y + self.height - 1)
        self.window.blit(self.next, genStr(bg, #self.next), genStr(other, #self.next))
    end

    if self.max_page > 1 and self.pageIndex > 1 then
        self.window.setCursorPos(self.x, self.y)
        self.window.blit(self.pre, genStr(bg, #self.pre), genStr(other, #self.pre))
    end
end

function context_pool:click(x, y)
    if y == self.y then
        self.pageIndex = self.pageIndex - 1 < 1 and self.max_page or self.pageIndex - 1
    elseif y == self.y + self.height - 1 then
        self.pageIndex = self.pageIndex + 1 > self.max_page and 1 or self.pageIndex + 1
    elseif self.list then
        local max_n = self.height - 2
        local startPoint = self.pageIndex == 1 and 0 or (self.pageIndex - 1) * max_n
        if self.list[startPoint + (y - self.y)] then
            self.target = self.list[startPoint + (y - self.y)]
        else
            self.target = nil
        end
    end
end

local new_context_pool = function(window, width, height, x, y)
    local ww = width % 2 == 0 and width / - 1 or width
    local half = genStr(" ", math.floor(ww / 2))
    local preButton = half .. "^" .. half
    local nextButton = half .. "v" .. half
    return setmetatable({ window = window, width = width, height = height, x = x, y = y, pre = preButton, next = nextButton, pageIndex = 1 }, {__index = context_pool})
end

--winIndex = 26
function recordings:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",   x = 1, y = 1, blitF = title,  blitB = bg },
        { text = "REC", x = 2, y = 10, blitF = "eee", blitB = "fff" },
        { text = "[<]", x = 6, y = 10, blitF = "8b8", blitB = "fff" },
        { text = "[>]", x = 13, y = 10, blitF = "8d8", blitB = "fff" }
    }
    self.delButton = { text = "[x]", x = 13, y = 1, blitF = "eee", blitB = "fff" }
    self.pool = new_context_pool(self.window, 13, 7, 2, 2)
end

function recordings:refresh()
    if replay_listener.isRunning then
        self.window.clear()
        if replay_listener.cd < 3 then
            self.window.setCursorPos(3, 5)
            self.window.blit(3 - replay_listener.cd .. " s to start", "dddddddddddd", "ffffffffffff")
        else
            self.window.setCursorPos(3, 5)
            self.window.blit("录制中..", "ddddddddddd", "fffffffffff")
        end
        
        self.window.setCursorPos(2, 7)
        self.window.blit("Touch to stop", "ddddddddddddd", "fffffffffffff")
    else
        self:refreshButtons()
        self:refreshTitle()

        self.window.setCursorPos(11, 10)
        if properties.autoReplay then
            self.window.blit("A", "8", "f")
        else
            self.window.blit("N", "8", "f")
        end

        local disk = peripheral.find("drive")
        if disk and disk.getMountPath() then
            if self.pool.target then
                self.window.setCursorPos(self.delButton.x, self.delButton.y)
                self.window.blit(self.delButton.text, self.delButton.blitF, self.delButton.blitB)
            end
            local len = math.floor((1 - fs.getFreeSpace("disk") / fs.getCapacity("disk")) * 13 + 0.5)
            self.window.setCursorPos(2, 9)
            self.window.blit(genStr(" ", len), genStr("f", len), genStr("5", len))
            len = 13 - len
            self.window.blit(genStr(" ", len), genStr("f", len), genStr("8", len))
            local records = fs.find("disk/recordings/*")
            local list = {}
            for k, v in pairs(records) do
                local name = split(v, "/")[3]
                table.insert(list, name)
            end
            self.pool:refresh(list)
        else
            self.window.setCursorPos(2, 6)
            self.window.blit("无磁盘", "eeeeeee", "fffffff")
        end
    end
end

local load_recordings = function(path)
    local records = fs.find(path.."/*.rec")
    local result = {}
    local fi = 1
    for k, v in pairs(records) do
        if fs.getSize(v) > 0 then
            local f = io.open(v, "r")
            local obj = textutils.unserialise(f:read("a"))
            f:close()
            if fi == 1 then
                result = obj
            else
                for i = 1, #obj, 1 do
                    table.insert(result, obj[i])
                end
            end
            fi = fi + 1
        else
            fs.delete(v)
        end
    end
    return result
end

function recordings:onTouch(x, y)
    self:subPage_Back(x, y)
    if y > 2 and replay_listener.isRunning then
        replay_listener:check()
    else
        if y == 10 then
            if x < 6 then
                replay_listener:check()
            elseif x < 9 then
                flight_control.replay_index = flight_control.replay_index == -1 and 0 or -1
                if self.pool.target and flight_control.replay_index == -1 then
                    if flight_control.recordings.name ~= self.pool.target then
                        flight_control.recordings = new_recordings(self.pool.target, load_recordings("disk/recordings/"..self.pool.target))
                    end
                end
            elseif x < 13 then
                properties.autoReplay = not properties.autoReplay
            else
                flight_control.replay_index = flight_control.replay_index == 1 and 0 or 1
                if self.pool.target and flight_control.replay_index == 1 then
                    if flight_control.recordings.name ~= self.pool.target then
                        flight_control.recordings = new_recordings(self.pool.target, load_recordings("disk/recordings/"..self.pool.target))
                    end
                end
            end
        elseif y > 1 and y < 10 then
            self.target = self.pool:click(x, y)
        elseif y == 1 and x > 12 and self.pool.target then
            fs.delete("disk/recordings/"..self.pool.target)
            self.pool.target = nil
        end
    end
end

--winIndex = 12
function set_simulate:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",             x = 1, y = 1, blitF = title,                       blitB = bg },
        { text = "AirMass-    +", x = 1, y = 3, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "Gravity-    +", x = 1, y = 5, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
        { text = "0_Point-    +", x = 1, y = 7, blitF = genStr(font, 7) .. "ffffff", blitB = genStr(bg, 7) .. "b" .. genStr(bg, 4) .. "e" },
    }
end

function set_simulate:refresh()
    self:refreshButtons()
    self:refreshTitle()
    local bg, other, font = properties.bg, properties.other, properties.font
    self.window.setTextColor(getColorDec(properties.font))
    self.window.setCursorPos(3, 1)
    self.window.blit(self.pageName, genStr(properties.title, #self.pageName), genStr(bg, #self.pageName))
    self.window.setCursorPos(9, 3)
    self.window.write(string.format("%0.1f", properties.airMass))
    self.window.setCursorPos(9, 5)
    self.window.write(string.format("%0.1f", properties.gravity))
    self.window.setCursorPos(9, 7)
    self.window.write(string.format("%d", properties.zeroPoint))
end

function set_simulate:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 2 and y > 2 and y < 10 then
        local result = 0
        if x == 8 then result = -0.1 end
        if x == 13 then result = 0.1 end
        if y == 3 then
            properties.airMass = properties.airMass + result < 0 and 0 or properties.airMass + result
        elseif y == 5 then
            properties.gravity = properties.gravity + result > 0 and 0 or properties.gravity + result
        elseif y == 7 then
            properties.zeroPoint = properties.zeroPoint == 0 and -1 or 0
        end
    end
end

--winIndex = 13
function set_other:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",            x = 1, y = 1,     blitF = title, blitB = bg },
        { text = "--      ++",    x = 5, y = 6,     blitF = "ffffffffff", blitB = "b5ffffff1e" }
    }
    self.canTp =    { text = "canTeleport", x = 2, y = 3,     blitF = genStr(bg, 11),    blitB = genStr(font, 11) }
    self.canNotTp = { text = "canTeleport", x = 2, y = 3,     blitF = genStr(font, 11),  blitB = genStr(bg, 11) }
end

function set_other:refresh()
    self:refreshButtons()
    self:refreshTitle()
    self.window.setCursorPos(self.canTp.x, self.canTp.y)
    if properties.canTeleport then
        self.window.blit(self.canTp.text, self.canTp.blitF, self.canTp.blitB)
    else
        self.window.blit(self.canNotTp.text, self.canNotTp.blitF, self.canNotTp.blitB)
    end
    
    self.window.setTextColor(colors.lightGray)
    self.window.setCursorPos(2, 5)
    self.window.write("pathFlowRange:")
    self.window.setCursorPos(2, 8)
    self.window.write("pathFlowMode:")
    self.window.setCursorPos(2, 6)
    self.window.setTextColor(colors.white)
    self.window.write(flight_control.shipLength .. "+")
    self.window.setCursorPos(9, 6)
    self.window.write(properties.pathRange)
    self.window.setCursorPos(2, 9)
    self.window.setBackgroundColor(colors.lightGray)
    self.window.setTextColor(colors.black)
    if properties.pathFollowMode == 1 then
        self.window.write("stepflow")
    else
        self.window.write("snake")
    end
end

function set_other:onTouch(x, y)
    self:subPage_Back(x, y)
    if y == 3 and x > 2 then
        properties.canTeleport = not properties.canTeleport
    elseif y == 6 then
        local result = 0
        result = x == 5 and -1 or x == 6 and -0.1 or x == 13 and 0.1 or x == 14 and 1 or 0
        properties.pathRange = properties.pathRange + result
    elseif y == 9 then
        properties.pathFollowMode = properties.pathFollowMode == 1 and 2 or 1
    end
end

--winIndex = 14
function set_profile:init()
    local bg, other, font, title = properties.bg, properties.other, properties.font, properties.title
    self.indexFlag = 4
    self.buttons = {
        { text = "<",        x = 1, y = 1, blitF = title,           blitB = bg },
        { text = "keyboard", x = 2, y = 3, blitF = genStr(font, 8), blitB = genStr(bg, 8) },
        { text = "joyStick", x = 2, y = 4, blitF = genStr(font, 8), blitB = genStr(bg, 8) }
    }
end

function set_profile:refresh()
    self:refreshButtons()
    self:refreshTitle()
    if properties.profileIndex == "keyboard" then
        self.window.setCursorPos(self.buttons[2].x, self.buttons[2].y)
        self.window.blit(self.buttons[2].text, genStr(properties.bg, 8), genStr(properties.select, 8))
    else
        self.window.setCursorPos(self.buttons[3].x, self.buttons[3].y)
        self.window.blit(self.buttons[3].text, genStr(properties.bg, 8), genStr(properties.select, 8))
    end
end

function set_profile:onTouch(x, y)
    self:subPage_Back(x, y)
    if x > 1 and x < 10 then
        if y == 3 then
            properties.profileIndex = "keyboard"
        elseif y == 4 then
            properties.profileIndex = "joyStick"
        end
    end
end

--winIndex = 15
function set_colortheme:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",          x = 1, y = 1, blitF = title,            blitB = bg },
        { text = "字体      ", x = 4, y = 3, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(font, 2) },
        { text = "背景", x = 4, y = 4, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(bg, 2) },
        { text = "标题     ", x = 4, y = 5, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(title, 2) },
        { text = "选择    ", x = 4, y = 6, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(select, 2) },
        { text = "其他     ", x = 4, y = 7, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(other, 2) },
    }
end

function set_colortheme:refresh()
    self:refreshButtons()
    self:refreshTitle()
    for i = 2, 6, 1 do
        self.window.setCursorPos(2, self.buttons[i].y)
        self.window.blit("  ", "00", self.buttons[i].prt)
    end
end

function set_colortheme:onTouch(x, y)
    self:subPage_Back(x, y)
    if x < 4 then
        if y == 3 then
            properties.font = getNextColor(properties.font, 1)
        elseif y == 4 then
            properties.bg = getNextColor(properties.bg, 1)
        elseif y == 5 then
            properties.title = getNextColor(properties.title, 1)
        elseif y == 6 then
            properties.select = getNextColor(properties.select, 1)
        elseif y == 7 then
            properties.other = getNextColor(properties.other, 1)
        end
    end
    self:init()
end

--winIndex=21
function mass_fix:init()
    local bg, font, title, select, other = properties.bg, properties.font, properties.title, properties.select,
        properties.other
    self.indexFlag = 4
    self.buttons = {
        { text = "<",          x = 1, y = 1, blitF = title,            blitB = bg },
        { text = "font      ", x = 4, y = 3, blitF = genStr(font, 10), blitB = genStr(bg, 10), prt = genStr(font, 2) },
    }
end

function mass_fix:refresh()
    self:refreshButtons()
    self:refreshTitle()
end

function mass_fix:onTouch(x, y)
    self:subPage_Back(x, y)
end

abstractMonitor = setmetatable({}, { __index = abstractScreen })

function abstractMonitor:getWindowCount()
    self.width, self.height = self.monitor.getSize()
    self.xCount = math.floor((self.width + 6) / 21.25 + 0.1)
    self.yCount = math.floor((self.height + 4) / 14.25 + 0.1)
end

function abstractMonitor:init()
    if self.monitor then
        if not self.name == "computer" then self.monitor.setTextScale(0.5) end
        self.monitor.setBackgroundColor(getColorDec(properties.bg))
        self.monitor.clear()
        self:getWindowCount()
        self.windows = {}
        self.winCount = 0
        local width, height = self.width / self.xCount, self.height / self.yCount
        width, height = math.floor(width), math.floor(height)
        local xP, yP = 1, 1
        for i = 1, self.yCount, 1 do
            table.insert(self.windows, {})
            for j = 1, self.xCount, 1 do
                table.insert(self.windows[i], {})
                self.winCount = self.winCount + 1
                for k = 1, #flightPages, 1 do
                    local tmpt = setmetatable(
                        {
                            name = self.name,
                            windows = self.windows,
                            winNum = self.winCount,
                            width = width,
                            height =
                                height,
                            row = i,
                            column = j
                        },
                        { __index = flightPages[k] }
                    )
                    xP = j > 1 and math.floor((j - 1) * width + 1.5) or 1
                    yP = i > 1 and math.floor((i - 1) * height + 1.5) or 1
                    tmpt:new(self.monitor, xP, yP, width, height, false)
                    tmpt:init()
                    table.insert(self.windows[i][j], tmpt)
                end
            end
        end

        if not properties.winIndex[self.name] then
            properties.winIndex[self.name] = {}
        end
        for i = 1, #self.windows, 1 do
            if not properties.winIndex[self.name][i] then
                properties.winIndex[self.name][i] = {}
            end
            for j = 1, #self.windows[i], 1 do
                if not properties.winIndex[self.name][i][j] then
                    properties.winIndex[self.name][i][j] = 1
                end
            end
        end
    end
end

function page_attach_manager:get(mName, pageName, row, column)
    if self[mName] then
        for k, v in pairs(self[mName][pageName]) do
            if (row >= v.rowStart and row <= v.rowEnd) and (column >= v.columnStart and column <= v.columnEnd) then
                v.row = row + 1 - v.rowStart
                v.maxRow = v.rowEnd + 1 - v.rowStart
                v.column = column + 1 - v.columnStart
                v.maxColumn = v.columnEnd + 1 - v.columnStart
                return v
            end
        end
    end
    return -1
end

function abstractMonitor:page_attach_util(name)
    local pageId
    if name == "attPage" then
        pageId = 3
    elseif name == "shipNetPage" then
        pageId = 2
    end
    local result = {}
    page_attach_manager[self.name][name] = result
    local wi = 1
    local group = 0
    while true do --总行数
        if wi > #self.windows then break end
        local minRowAdd = 0
        local wj = 1
        while true do --总列数
            if wj > #self.windows[1] then break end
            local rowStart, columnStart = wi, wj
            local rowEnd = rowStart + 2 > #self.windows and #self.windows or rowStart + 2
            local columnEnd = columnStart + 2 > #self.windows[1] and #self.windows[1] or columnStart + 2
            local rowCount, columnCount = 0, 0
            for i = rowStart, rowEnd, 1 do
                local j = columnStart
                while true do
                    if j > columnEnd then break end
                    if properties.winIndex[self.name][i][j] == pageId then
                        if i == rowStart then columnCount = columnCount + 1 end
                    else
                        if i == rowStart and columnCount > 0 then
                            columnEnd = j - 1
                        elseif j < columnEnd + 1 then
                            goto continue
                        end
                    end
                    j = j + 1
                end
                rowCount = rowCount + 1
            end
            ::continue::
            if rowCount > 1 or columnCount > 1 then
                group = group + 1
                table.insert(result, {
                    group = group,
                    rowStart = rowStart,
                    rowEnd = rowStart - 1 + rowCount,
                    columnStart = columnStart,
                    columnEnd = columnStart - 1 + columnCount
                })
            end
            wj = columnCount == 0 and wj + 1 or wj + columnCount
            if wi == 1 then
                minRowAdd = rowCount
            else
                minRowAdd = math.min(minRowAdd, rowCount)
            end
        end
        wi = minRowAdd == 0 and wi + 1 or wi + minRowAdd
    end
end

function abstractMonitor:refresh_page_attach()
    if not page_attach_manager[self.name] then
        page_attach_manager[self.name] = {}
    end
    self:page_attach_util("attPage")
    self:page_attach_util("shipNetPage")
end

function abstractMonitor:refresh()
    self:refresh_page_attach()
    for i = 1, #self.windows, 1 do
        for j = 1, #self.windows[i], 1 do
            for k = 1, #self.windows[i][j], 1 do
                if properties.winIndex[self.name][i][j] == k then
                    self.windows[i][j][k].window.setVisible(true)
                    self.windows[i][j][k]:refresh()
                else
                    self.windows[i][j][k].window.setVisible(false)
                end
            end
        end
    end
end

function abstractMonitor:onTouch(x, y)
    local clickX, clickY
    for i = #self.windows, 1, -1 do
        local xPos, yPos = self.windows[i][1][1].window.getPosition()
        if yPos <= y then
            clickY = math.floor(y % self.windows[i][1][1].height)
            clickY = clickY == 0 and math.floor(self.windows[i][1][1].height) or clickY
            for j = #self.windows[i], 1, -1 do
                xPos, yPos = self.windows[i][j][1].window.getPosition()
                if xPos <= x then
                    clickX = x % self.windows[i][j][1].width
                    clickX = clickX == 0 and math.floor(self.windows[i][1][1].width) or clickX
                    self.windows[i][j][properties.winIndex[self.name][i][j]]:onTouch(clickX, clickY)
                    return
                end
            end
        end
    end
end

-- flightGizmoScreen
-- 飞控系统屏幕

local flightGizmoScreen = setmetatable({ screenTitle = "gizmo", }, { __index = abstractMonitor })

function flightGizmoScreen:report()
    return ("%d x %d"):format(#self.windows, #self.windows[1])
end

-- screensManagerScreen
-- 用于管理所有其他的屏幕；主机专属屏幕
local screensManagerScreen = {
    screenTitle = "屏幕管理器"
}
screensManagerScreen.__index = setmetatable(screensManagerScreen, abstractScreen)

function screensManagerScreen:init()
    self.rows = {}
end

function screensManagerScreen:refresh()
    local redirects = arrayTableDuplicate(joinArrayTables({ "computer" }, monitorUtil.getMonitorNames(),
        properties.enabledMonitors))
    table.sort(redirects, function(n1, n2)
        local s1 = monitorUtil.getMonitorSort(n1)
        local s2 = monitorUtil.getMonitorSort(n2)
        if s1 ~= s2 then
            return s1 < s2
        else
            return n1 < n2
        end
    end)
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write("Monitors:")
    local newrows = {}
    for i, name in ipairs(redirects) do
        self.monitor.setCursorPos(1, i + 1)
        self.monitor.write(("%2i."):format(i))
        local status
        local title
        local report
        if not monitorUtil.hasMonitor(name) then
            self.monitor.setBackgroundColor(colors.red)
            status = "缺失"
        elseif not tableHasValue(properties.enabledMonitors, name) then
            self.monitor.setBackgroundColor(colors.lightGray)
            status = "离线"
        elseif not monitorUtil.screens[name] then
            self.monitor.setBackgroundColor(colors.red)
            status = "在线"
            title = "???"
        else
            local text, color = monitorUtil.screens[name]:report()
            self.monitor.setBackgroundColor(color or colors.lime)
            status = "在线"
            title = monitorUtil.screens[name].screenTitle
            report = text
        end
        table.insert(newrows, name)
        if name == "computer" then
            name = os.getComputerLabel() or name
        end
        self.monitor.write(name .. " [" .. status .. "]")
        if title then
            self.monitor.write("[" .. title .. "]")
        end
        if report then
            self.monitor.write("[" .. report .. "]")
        end
        self.monitor.setBackgroundColor(colors.black)
    end
    self.rows = newrows
end

function screensManagerScreen:onTouch(x, y)
    local name = self.rows[y - 1]
    if name then
        if tableHasValue(properties.enabledMonitors, name) then
            arrayTableRemoveElement(properties.enabledMonitors, name)
            monitorUtil.disconnect(name)
        else
            table.insert(properties.enabledMonitors, name)
        end
        system:updatePersistentData()
    end
end

local absHologramSetPage = setmetatable({ screenTitle = "全息图设置" }, {__index=abstractScreen})
local hologramManagerScreen = setmetatable({ screenTitle = "全息图管理器" }, {__index=abstractScreen})

function absHologramSetPage:init()
    
end

function absHologramSetPage:refresh()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.blit("< back ", "fffffff", "0000000")
    local prop = hologram_prop[self.holo]
    self.monitor.setCursorPos(2, 3)
    self.monitor.blit(("scale --%5.1f++"):format(prop.scale), "00000ffffffffff", "ffffffb5000001e")
    self.monitor.setCursorPos(2, 5)
    self.monitor.blit(("width  + %5d -"):format(prop.width), "00000fffffffffff", "fffffffb0000000e")
    self.monitor.setCursorPos(2, 6)
    self.monitor.blit(("height - %5d -"):format(prop.height), "000000ffffffffff", "fffffffb0000000e")
    self.monitor.setCursorPos(2, 12)
    self.monitor.blit(("radar  - %5d -"):format(properties.radarRange), "000000ffffffffff", "fffffffb0000000e")
    self.monitor.setCursorPos(2, 8)
    if prop.drawHoloBorder then
        self.monitor.blit("绘制边框", "fffffffffff", "00000000000")
    else
        self.monitor.blit("绘制边框", "00000000000", "fffffffffff")
    end
    self.monitor.setCursorPos(2, 9)
    if prop.drawInputLine then
        self.monitor.blit("输入线", "ffffffffff", "0000000000")
    else
        self.monitor.blit("输入线", "0000000000", "ffffffffff")
    end
    self.monitor.setCursorPos(2, 10)
    if prop.rgb_lock_box then
        self.monitor.blit("RGB锁定框", "ffffffffffff", "000000000000")
    else
        self.monitor.blit("RGB锁定框", "000000000000", "ffffffffffff")
    end
    self.monitor.setCursorPos(2, 11)
    if prop.other_targets then
        self.monitor.blit("其他目标", "ffffffffffff", "000000000000")
    else
        self.monitor.blit("其他目标", "000000000000", "ffffffffffff")
    end
    self.monitor.setCursorPos(28, 3)
    self.monitor.blit(("translation_x --%5.1f++"):format(prop.translation.x), genStr("0", 14).."fffffffff", genStr("f", 14).."b5000001e")
    self.monitor.setCursorPos(28, 4)
    self.monitor.blit(("translation_y --%5.1f++"):format(prop.translation.y), genStr("0", 14).."fffffffff", genStr("f", 14).."b5000001e")
    self.monitor.setCursorPos(28, 5)
    self.monitor.blit(("translation_z --%5.1f++"):format(prop.translation.z), genStr("0", 14).."fffffffff", genStr("f", 14).."b5000001e")
    self.monitor.setCursorPos(2, 14)
    self.monitor.blit(("background_r -- %3d ++"):format(prop.bg.r), genStr("f", 22), genStr("e", 12).."fb5000001e")
    self.monitor.setCursorPos(2, 15)
    self.monitor.blit(("background_g -- %3d ++"):format(prop.bg.g), genStr("f", 22), genStr("5", 12).."fb5000001e")
    self.monitor.setCursorPos(2, 16)
    self.monitor.blit(("background_b -- %3d ++"):format(prop.bg.b), genStr("f", 22), genStr("b", 12).."fb5000001e")
    self.monitor.setCursorPos(2, 17)
    self.monitor.blit(("background_a -- %3d ++"):format(prop.bg.a), genStr("f", 22), genStr("0", 12).."fb5000001e")
    self.monitor.setCursorPos(28, 9)
    self.monitor.blit(("eye_offset_x --%5.1f++"):format(prop.eye_offset.x), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 10)
    self.monitor.blit(("eye_offset_y --%5.2f++"):format(prop.eye_offset.y), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 12)
    self.monitor.blit(("att_border   --%5.2f++"):format(prop.attBorder.y), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 13)
    self.monitor.blit(("att_size     --%5.2f++"):format(prop.attSize), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 7)
    self.monitor.blit(("PitchInterval - %3d +"):format(prop.lint_interval), genStr("0", 13).."ffffffff", genStr("f", 14).."b00000e")
    self.monitor.setCursorPos(28, 15)
    self.monitor.blit(("m_bar_offset --%5.2f++"):format(prop.msg_bar_offset), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 16)
    self.monitor.blit(("g_bar_offset --%5.2f++"):format(prop.cannon_bar_offset), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
    self.monitor.setCursorPos(28, 17)
    self.monitor.blit(("t_bar_offset --%5.2f++"):format(prop.target_bar_offset), genStr("0", 13).."fffffffff", genStr("f", 13).."b5000001e")
end

function absHologramSetPage:checkInputRange(t, min, max)
    return t < min and min or t > max and max or t
end

function absHologramSetPage:onTouch(x, y)
    if x < 8 and y == 1 then
        monitorUtil.newScreen(self.name, nil)
    else
        local prop = hologram_prop[self.holo]
        if x < 28 then
            if y == 3 then
                local result = x == 8 and -1 or x == 9 and -0.1 or x == 15 and 0.1 or x == 16 and 1 or 0
                prop.scale = self:checkInputRange(prop.scale + result, 0.1, 99)
            elseif y == 5 then
                local result = x == 9 and -64 or x == 17 and 64 or 0
                prop.width = self:checkInputRange(prop.width + result, 64, 1024)
                if prop.width < prop.height  then prop.height = prop.width end
            elseif y == 6 then
                local result = x == 9 and -64 or x == 17 and 64 or 0
                prop.height = self:checkInputRange(prop.height + result, 64, prop.width)
            elseif y == 8 then
                if x < 13 then
                    prop.drawHoloBorder = not prop.drawHoloBorder
                end
            elseif y == 9 then
                if x < 13 then
                    prop.drawInputLine = not prop.drawInputLine
                end
            elseif y == 10 then
                if x < 13 then
                    prop.rgb_lock_box = not prop.rgb_lock_box
                end
            elseif y == 11 then
                if x < 13 then
                    prop.other_targets = not prop.other_targets
                end
            elseif y == 12 then
                local result = x == 9 and -64 or x == 17 and 64 or 0
                properties.radarRange = result + properties.radarRange
                properties.radarRange = properties.radarRange > 2496 and 2496 or properties.radarRange < 64 and 64 or properties.radarRange
            end
        else
            if y == 3 then
                local result = x == 42 and -1 or x == 43 and -0.1 or x == 49 and 0.1 or x == 50 and 1 or 0
                prop.translation.x = result + prop.translation.x
                prop.translation.x = math.abs(prop.translation.x) > 99 and copysign(99, prop.translation.x) or prop.translation.x
            elseif y == 4 then
                local result = x == 42 and -1 or x == 43 and -0.1 or x == 49 and 0.1 or x == 50 and 1 or 0
                prop.translation.y = result + prop.translation.y
                prop.translation.y = math.abs(prop.translation.y) > 99 and copysign(99, prop.translation.y) or prop.translation.y
            elseif y == 5 then
                local result = x == 42 and -1 or x == 43 and -0.1 or x == 49 and 0.1 or x == 50 and 1 or 0
                prop.translation.z = result + prop.translation.z
                prop.translation.z = math.abs(prop.translation.z) > 99 and copysign(99, prop.translation.z) or prop.translation.z
            elseif y == 7 then
                local result = x == 42 and -1 or x == 48 and 1 or 0
                prop.lint_interval = self:checkInputRange(prop.lint_interval + result, 1, 30)
            elseif y == 9 then
                local result = x == 41 and -1 or x == 42 and -0.1 or x == 48 and 0.1 or x == 49 and 1 or 0
                prop.eye_offset.x = self:checkInputRange(prop.eye_offset.x + result, 0, 30)
            elseif y == 10 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.eye_offset.y = result + prop.eye_offset.y
                prop.eye_offset.y = math.abs(prop.eye_offset.y) > 99 and copysign(99, prop.eye_offset.y) or prop.eye_offset.y
            elseif y == 12 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.attBorder.y = result + prop.attBorder.y
                prop.attBorder.y = math.abs(prop.attBorder.y) > 0.5 and copysign(0.5, prop.attBorder.y) or prop.attBorder.y
                prop.attBorder.y = prop.attBorder.y < 0 and 0 or prop.attBorder.y
                prop.attBorder.x = prop.attBorder.y
            elseif y == 13 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.attSize = result + prop.attSize
                prop.attSize = prop.attSize < 0.1 and 0.1 or prop.attSize > 3 and 3 or prop.attSize
            elseif y == 15 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.msg_bar_offset = result + prop.msg_bar_offset
                prop.msg_bar_offset = prop.msg_bar_offset < -0.95 and -0.95 or prop.msg_bar_offset > 0.95 and 0.95 or prop.msg_bar_offset
            elseif y == 16 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.cannon_bar_offset = result + prop.cannon_bar_offset
                prop.cannon_bar_offset = prop.cannon_bar_offset < -0.95 and -0.95 or prop.cannon_bar_offset > 0.95 and 0.95 or prop.cannon_bar_offset
            elseif y == 17 then
                local result = x == 41 and -0.1 or x == 42 and -0.01 or x == 48 and 0.01 or x == 49 and 0.1 or 0
                prop.target_bar_offset = result + prop.target_bar_offset
                prop.target_bar_offset = prop.target_bar_offset < -0.99 and -0.99 or prop.target_bar_offset > 0.99 and 0.99 or prop.target_bar_offset
            end
        end
        system:updatePersistentData()
        hologram_manager:initAll()
    end
end

function hologramManagerScreen:init()
    self.holograms = {}
    for k, v in pairs(hologram_manager.holograms) do
        table.insert(self.holograms, setmetatable({ holo = v.name }, {__index = absHologramSetPage}))
    end
end

function hologramManagerScreen:refresh()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write(" <")
    local cursorIndex = 2
    for k, v in pairs(self.holograms) do
        self.monitor.setBackgroundColor(colors.blue)
        self.monitor.setCursorPos(2, cursorIndex)
        self.monitor.write(string.format("[ %s ]", v.holo))
        cursorIndex = cursorIndex + 1
    end
end

function hologramManagerScreen:onTouch(x, y)
    if y > #self.holograms + 1 then
        return
    end
    if y < 2 then
        monitorUtil.newScreen(self.name, nil)
    end
    monitorUtil.newScreen(self.name, self.holograms[y - 1])
end

-- screenPickerScreen
-- 用于打开其他的屏幕的屏幕
local screenPickerScreen = setmetatable({ screenTitle = "idle" }, { __index = abstractScreen })

function screenPickerScreen:init()
    self.rows = {}
    if self.name == "computer" then
        table.insert(self.rows, { name = "屏幕管理器", class = screensManagerScreen })
        table.insert(self.rows, { name = "全息图管理器", class = hologramManagerScreen })
    end
    table.insert(self.rows, { name = "飞行小工具", class = flightGizmoScreen })
    if #self.rows == 1 then
        monitorUtil.newScreen(self.name, self.rows[1].class)
        return
    end
end

function screenPickerScreen:refresh()
    self.monitor.setTextColor(colors.white)
    self.monitor.setBackgroundColor(colors.black)
    self.monitor.clear()
    self.monitor.setCursorPos(1, 1)
    self.monitor.write("Choose screen:")

    self.monitor.setCursorPos(41, 2)
    self.monitor.write(string.format("Face: %s", properties.shipFace))
    self.monitor.setCursorPos(34, 1)
    self.monitor.write(string.format("Computer_Id: %4d", computerId))
    if #self.rows <= 0 then
        self.monitor.setCursorPos(1, 2)
        self.monitor.write("no screen available!")
    else
        for i, row in ipairs(self.rows) do
            self.monitor.setCursorPos(1, i + 1)
            self.monitor.write(("%2i."):format(i))
            if i % 2 == 0 then
                self.monitor.setBackgroundColor(colors.lightGray)
            else
                self.monitor.setBackgroundColor(colors.gray)
            end
            self.monitor.write(row.name)
            self.monitor.setBackgroundColor(colors.black)
        end
    end
end

function screenPickerScreen:onTouch(x, y)
    local row = self.rows[y - 1]
    if row then
        monitorUtil.newScreen(self.name, row.class)
    end
end

-- loadingScreen
-- 加载屏幕
local loadingScreen = {
    screenTitle = "加载中"
}
loadingScreen.__index = setmetatable(loadingScreen, abstractScreen)

function loadingScreen:report()
    return "Loading: " .. ("%i"):format(math.floor(self.step * 100 / 16)) .. "%", colors.orange
end

function loadingScreen:init()
    self.step = 1
    self.index = 1
    self.postload = screenPickerScreen
    if self.monitor.setTextScale then -- 电脑终端也是显示器
        self.monitor.setTextScale(0.5)
    end
end

function loadingScreen:refresh()
    if self.step == 1 then
        local offset_x, offset_y = self.monitor.getSize()
        offset_x = math.floor((offset_x - 15) / 2)
        offset_y = math.floor((offset_y - 10) / 2)
        self.monitor.setBackgroundColor(colors.black)
        self.monitor.clear()
        self.monitor.setCursorBlink(false)
        self.monitor.setCursorPos(offset_x + 5, offset_y + 3)
        self.monitor.blit("欢迎", "0000000", "fffffff")

        self.monitor.setCursorPos(offset_x + 9 - #properties.userName / 2, offset_y + 5)
        self.monitor.write(properties.userName)

        self.monitor.setCursorPos(offset_x + 9 - #self.name / 2 - 1, offset_y + 9)
        self.monitor.write("[" .. self.name .. "]")

        self.monitor.setCursorPos(offset_x + 1, offset_y + 7)
    end
    if self.index >= 14 then self.index = 1 end
    if self.index < 10 then
        self.monitor.blit("-", ("%d"):format(self.index), "f")
    else
        self.monitor.blit("-", ("%s"):format(string.char(self.index + 87)), "f")
    end
    self.step = self.step + 1
    self.index = self.index + 1
    if self.step >= 16 then
        monitorUtil.newScreen(self.name, self.postload)
        for k, screen in pairs(monitorUtil.screens) do
            screen:refresh()
        end
    end
end

---------monitorUtil---------
monitorUtil = {
    screens = {}
}

monitorUtil.newScreen = function(name, class)
    if not class then
        class = loadingScreen
    end
    local screen = setmetatable({ name = name }, { __index = class })
    monitorUtil.screens[name] = screen
    local monitor
    if name == "computer" then
        monitor = term.current()
    else
        monitor = peripheral.wrap(name)
    end
    screen.monitor = monitor
    screen:init()
    return monitorUtil.screens[name] --init有可能会改变屏幕类
end

monitorUtil.disconnectComputer = function()
    local c = term.current()
    c.setTextColor(colors.white)
    c.setBackgroundColor(colors.black)
    c.clear()
    c.setCursorPos(1, 1)
    c.write("[DISCONNECTED]")
    c.setCursorPos(1, 2)
    c.write("Press any key to reconnect this screen...")
    c.setCursorPos(1, 3)
end

monitorUtil.disconnect = function(name)
    if monitorUtil.screens[name] ~= nil and (name == "computer" or peripheral.isPresent(name)) then
        monitorUtil.screens[name]:onDisconnect()
        monitorUtil.screens[name] = nil
        if name == "computer" then
            monitorUtil.disconnectComputer()
        end
    end
end

monitorUtil.hasMonitor = function(name)
    if name == "computer" then
        return term.current().isColor()
    elseif peripheral.isPresent(name) and peripheral.hasType(name, "monitor") then
        return true
    else
        return false
    end
end

monitorUtil.scanMonitors = function()
    for _, name in ipairs(properties.enabledMonitors) do
        if monitorUtil.screens[name] == nil then
            if name == "computer" or monitorUtil.hasMonitor(name) then
                monitorUtil.newScreen(name)
            end
        end
    end
    for _, name in ipairs(properties.enabledMonitors) do
        if not monitorUtil.hasMonitor(name) then
            monitorUtil.screens[name] = nil
        elseif not tableHasValue(properties.enabledMonitors, name) then
            monitorUtil.disconnect(name)
        end
    end
end

monitorUtil.refresh = function()
    monitorUtil.scanMonitors()
    for n, screen in pairs(monitorUtil.screens) do
        if screen.windows then
            for i = 1, #screen.windows, 1 do
                for j = 1, #screen.windows[i], 1 do
                    local page = properties.winIndex[n][i][j]
                    if page == 2 or page == 3 or page == 16 then
                        screen.windows[i][j][page]:refresh()
                    end
                end
            end
        elseif screen.step then
            screen:refresh()
        end
    end
end

monitorUtil.refreshAll = function()
    monitorUtil.scanMonitors()
    for _, screen in pairs(monitorUtil.screens) do
        screen:refresh()
    end
end

monitorUtil.getMonitorNames = function()
    local monitors = peripheral.getNames()
    local result = {}
    table.insert(monitors, "term")
    for _, name in ipairs(monitors) do
        if monitorUtil.hasMonitor(name) then
            table.insert(result, name)
        end
    end
    return result
end

monitorUtil.blankAllScreens = function()
    for _, screen in pairs(monitorUtil.screens) do
        screen:onBlank()
    end
end

monitorUtil.onRootFatal = function()
    for _, screen in pairs(monitorUtil.screens) do
        screen:onRootFatal()
    end
end


monitorUtil.onSystemSleep = function()
    for _, screen in pairs(monitorUtil.screens) do
        screen:onSystemSleep()
    end
end

monitorUtil.monitorSortOrder = {
    computer = -8,
    bottom = -7,
    top = -6,
    left = -5,
    right = -4,
    front = -3,
    back = -2,
}

function monitorUtil.getMonitorSort(name)
    if monitorUtil.monitorSortOrder[name] then
        return monitorUtil.monitorSortOrder[name]
    end
    local id = string.match(name, 'monitor_(%d+)')
    if id then
        return tonumber(id) or -1
    end
    return -1
end


---------broadcast---------
beat_ct, call_ct, captcha, calling = 5, 0, genCaptcha(), -1
local shipNet_beat = function() --广播
    local auto_connect_cd = 2
    while true do
        if not shutdown_flag then
            ---------发送广播---------
            local broadcast_msg = {
                name = shipName,
                id = computerId,
                request_connect = "broadcast",
                pos = engine_controller.getPosition(),
            }
            rednet.broadcast(broadcast_msg, public_protocol)

            ---------公频广播心跳包---------
            local index = 1
            while true do
                if index > #shipNet_list then break end
                shipNet_list[index].beat = shipNet_list[index].beat - 1
                if shipNet_list[index].beat < 1 then
                    if shipNet_list[index].id == parentShip.id then
                        parentShip.id = -1
                    end
                    table.remove(shipNet_list, index)
                    index = index - 1
                end

                index = index + 1
            end

            ---------父级飞船心跳包---------
            if parentShip.id ~= -1 then
                parentShip.beat = parentShip.beat - 1
                if parentShip.beat <= 0 then
                    parentShip.id = -1
                end
            end

            ---------子级飞船心跳包---------
            local i2 = 1
            while true do
                if i2 > #childShips then break end
                childShips[i2].beat = childShips[i2].beat - 1
                if childShips[i2].beat <= 0 then
                    table.remove(childShips, i2)
                    i2 = i2 - 1
                end
                i2 = i2 + 1
            end

            if parentShip.id ~= -1 then --给父级发送心跳包
                shipNet_p2p_send(parentShip.id, "beat")
            end

            for k, v in pairs(childShips) do --给子级发送心跳包
                shipNet_p2p_send(v.id, "beat")
            end

            ---------呼叫计时器---------
            if call_ct > 0 then --已在呼叫中
                call_ct = call_ct - 1
            else                --未在呼叫或呼叫超时
                call_ct = 0
                calling = -1
            end

            ---------呼叫请求计时器---------
            if #callList > 0 then --未处理的呼叫请求
                for k, v in pairs(callList) do
                    v.ct = v.ct - 1
                end
                if callList[1].ct <= 0 then --未处理请求超时
                    table.remove(callList, 1)
                end
            end

            ---------自动连接计时器---------
            if parentShip.id == -1 and properties.lastParent ~= -1 then --未连接父级且自动连接超时
                if auto_connect_cd == 0 then
                    shipNet_p2p_send(properties.lastParent, "call")
                end
                auto_connect_cd = auto_connect_cd + 1
                auto_connect_cd = auto_connect_cd > 3 and 0 or auto_connect_cd
            end
            
        end
        sleep(1)
    end
end

local shipNet_getMessage = function() --从广播中筛选
    while true do
        if not shutdown_flag then
            local id, msg = rednet.receive(public_protocol)     --船舶信息广播

            if id == parentShip.id and msg.code == captcha then --父级飞船发来的消息
                if msg.pos then
                    parentShip = msg
                    properties.pathFollowMode = msg.pathFollowMode
                    parentShip.beat = beat_ct
                end
            end

            if msg == "beat" then
                for k, v in pairs(childShips) do
                    if id == v.id then
                        v.beat = beat_ct
                    end
                end
            end

            if type(msg) == "table" then
                if msg.request_connect == "broadcast" then --收到公频广播
                    local flag = false
                    for i = 1, #shipNet_list, 1 do
                        if table.contains(shipNet_list[i], msg.name) then
                            msg.beat = beat_ct
                            shipNet_list[i] = msg
                            flag = true
                            break
                        end
                    end
                    if not flag then
                        msg.beat = beat_ct
                        table.insert(shipNet_list, msg)
                    end
                elseif msg.request_connect == "call" and msg.name and msg.code then --收到连接请求
                    local result = { id = id, name = msg.name, code = msg.code, ct = 20 }
                    local flag = false
                    for k, v in pairs(properties.shipNet_whiteList) do
                        if msg.name == v then
                            accept_connect(result, msg.code)
                            flag = true
                            break
                        end
                    end

                    for k, v in pairs(callList) do
                        if v.id == result.id then
                            callList[k].ct = 20
                            flag = true
                            break
                        end
                    end
                    
                    if not flag then
                        table.insert(callList, result)
                    end
                    monitorUtil.refreshAll()
                elseif msg.request_connect == "back" and msg.name and msg.code == captcha then --回听请求是否被接受
                    if msg.result == "agree" then
                        parentShip.id = id
                        parentShip.name = msg.name
                        parentShip.beat = beat_ct
                        parentShip.code = captcha
                        parentShip.pos = msg.pos
                        parentShip.size = msg.size
                        parentShip.scale = 1
                        parentShip.rot = DEFAULT_PARENT_SHIP.rot
                        parentShip.preQuat = DEFAULT_PARENT_SHIP.rot
                        parentShip.velocity = DEFAULT_PARENT_SHIP.velocity
                        parentShip.anchorage = DEFAULT_PARENT_SHIP.anchorage
                        properties.lastParent = id
                        system:updatePersistentData()
                    else
                        parentShip.id = -1
                    end
                    call_ct = 0
                    calling = -1
                    monitorUtil.refreshAll()
                end
            end
        else
            sleep(0.5)
        end
    end
end

local shipNet_run = function() --启动船舶网络
    sleep(0.1)
    parallel.waitForAll(shipNet_beat, shipNet_getMessage)
end

shipNet_p2p_send = function(id, type, code) --发送p2p
    if type == "call" then            --请求父级连接
        if call_ct <= 0 and id ~= parentShip.id then
            rednet.send(id, { name = shipName, code = captcha, request_connect = "call" }, public_protocol)
            calling = id
            call_ct = 2
        end
    elseif type == "agree" or type == "refuse" then --回复子级连接
        local result = { name = shipName, code = code, request_connect = "back", result = type }
        if type == "agree" then
            result.pos = flight_control.pos
            result.size = flight_control.size
            result.rot = flight_control.rot
        end
        rednet.send(id, result, public_protocol)
    elseif type == "beat" then --向父级发送心跳包
        rednet.send(id, "beat", public_protocol)
    end
end

-- {name = properties.cannonName, pw = properties.password}
local cannon_rednet = function()
    while true do
        local id, msg
        repeat
            id, msg = rednet.receive(request_protocol)
        until type(msg) == "table"
        if msg.pw == properties.password then
            local flag = false
            for k, v in pairs(linkedCannons) do
                if v.id == id then
                    v.beat = 3
                    v.bullets_count = msg.bullets_count
                    if msg.cross_point then
                        v.cross_point = newVec(msg.cross_point)
                    else
                        v.cross_point = nil
                    end
                    flag = true
                    break
                end
            end

            if not flag then
                table.insert(linkedCannons, {
                    id = id,
                    name = filterString(msg.name),
                    beat = 3,
                    mode = 2,
                    bullets_count = msg.bullets_count,
                    group = nil
                })

                if not table.contains(properties.whiteList, msg.slug) then
                    table.insert(properties.whiteList, msg.slug)
                end
                
                if not table.contains(properties.whiteList, msg.yawSlug) then
                    table.insert(properties.whiteList, msg.yawSlug)
                end

                if msg.pitchSlug and not table.contains(properties.whiteList, msg.pitchSlug) then
                    table.insert(properties.whiteList, msg.pitchSlug)
                end
                system:updatePersistentData()
            end
        end
    end
end
    
local beats = function()
    while true do
        local index = 1
        while true do
            if index > #linkedCannons then
                break
            end
            linkedCannons[index].beat = linkedCannons[index].beat - 1
            if linkedCannons[index].beat < 0 then
                table.remove(linkedCannons, index)
                index = index - 1
            end
            index = index + 1
        end
        sleep(1)
    end
end

local run_fire_control = function ()
    parallel.waitForAll(cannon_rednet, beats)
end
----------------------------------------------------

local run_event = function ()
    while true do
        local eventData = {os.pullEvent()}
        local event = eventData[1]
        if event == "phys_tick" then
            flight_control:run(eventData[2])
        else
            if event == "monitor_touch" and monitorUtil.screens[eventData[2]] then
                if shutdown_flag then
                    local m = monitorUtil.screens[eventData[2]].monitor
                    local x, y = m.getSize()
                    if eventData[4] == y / 2 and eventData[3] >= x / 2 - 7 and eventData[3] <= x / 2 + 9 then
                        os.reboot()
                    end
                end
                monitorUtil.screens[eventData[2]]:onTouch(eventData[3], eventData[4])
                for k, screen in pairs(monitorUtil.screens) do
                    screen:refresh()
                end
                system:updatePersistentData()
            elseif event == "mouse_click" and monitorUtil.screens["computer"] then
                monitorUtil.screens["computer"]:onTouch(eventData[3], eventData[4])
                for k, screen in pairs(monitorUtil.screens) do
                    screen:refresh()
                end
                system:updatePersistentData()
            elseif event == "key" and not tableHasValue(properties.enabledMonitors, "computer") then
                table.insert(properties.enabledMonitors, "computer")
                system:updatePersistentData()
            elseif event == "peripheral" or event == "disk" or event == "disk_eject" then
                controllers:getAll()
                hologram_manager:initAll()
                monitorUtil.refreshAll()
            end
        end
    end
end

local refreshDisplay = function()
    sleep(0.1)
    while true do
        if shutdown_flag then
            monitorUtil.onSystemSleep()
            sleep(0.5)
        else
            monitorUtil.refresh()
            sleep(0.05)
        end
    end
end

local run_hologram = function ()
    sleep(0.1)
    local need_init = true
    while true do
        if ship and ship.isStatic() then
            sleep(0.5)
            need_init = true
        else
            if need_init then
                hologram_manager:getAllHoloGram()
                need_init = false
            end
            engine_controller.setIdle(false)
            hologram_manager:refresh()
            sleep(0.05)
        end
    end
end

local run_radar = function()
    sleep(1)
    radar:run()
end

local run_Controllers = function ()
    controllers:getAll()
    controllers:run()
end

local run = function ()
    flight_control.lastPos = engine_controller.getPosition()
    parallel.waitForAll(run_event, run_radar, run_fire_control, run_Controllers, run_hologram, shipNet_run)
end

system:init()
xpcall(function()
    monitorUtil.scanMonitors()
    if monitorUtil.screens["computer"] == nil then
        monitorUtil.disconnectComputer()
    end
    parallel.waitForAll(run, refreshDisplay)
    error("Unexpected flight control exit")
end, function(err)
    monitorUtil.onRootFatal()
    local c = term.current()
    c.setTextColor(colors.white)
    c.setBackgroundColor(colors.black)
    c.clear()
    c.setCursorPos(1, 1)
    if c.setTextScale then
        c.setTextScale(1)
    end
    if err:find("Terminated") then
        c.setTextColor(colors.orange)
        print("Flight control terminated.")
    else
        c.setTextColor(colors.red)
        print("Flight control error:")
        print(err)
    end
    c.setTextColor(colors.white)
end)
